diff --git a/core/bump_objects.ts b/core/bump_objects.ts index 51467ae25..cd2c754a4 100644 --- a/core/bump_objects.ts +++ b/core/bump_objects.ts @@ -147,16 +147,16 @@ function extractObjectFromEvent( switch (e.type) { case eventUtils.BLOCK_CREATE: case eventUtils.BLOCK_MOVE: - object = workspace.getBlockById((e as BlockCreate | BlockMove).blockId); + object = workspace.getBlockById((e as BlockCreate | BlockMove).blockId!); if (object) { object = object.getRootBlock(); } break; case eventUtils.COMMENT_CREATE: case eventUtils.COMMENT_MOVE: - object = workspace.getCommentById( - (e as CommentCreate | CommentMove).commentId) as - WorkspaceCommentSvg | + object = + workspace.getCommentById((e as CommentCreate | CommentMove).commentId! + ) as WorkspaceCommentSvg | null; break; } diff --git a/core/events/events.ts b/core/events/events.ts index cc0093111..a2bcc8332 100644 --- a/core/events/events.ts +++ b/core/events/events.ts @@ -13,64 +13,90 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events'); -import {Abstract as AbstractEvent} from './events_abstract.js'; -import {BlockBase} from './events_block_base.js'; -import {BlockChange} from './events_block_change.js'; -import {BlockCreate} from './events_block_create.js'; -import {BlockDelete} from './events_block_delete.js'; -import {BlockDrag} from './events_block_drag.js'; -import {BlockMove} from './events_block_move.js'; -import {BubbleOpen} from './events_bubble_open.js'; -import {Click} from './events_click.js'; -import {CommentBase} from './events_comment_base.js'; -import {CommentChange} from './events_comment_change.js'; -import {CommentCreate} from './events_comment_create.js'; +import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js'; +import {BlockBase, BlockBaseJson} from './events_block_base.js'; +import {BlockChange, BlockChangeJson} from './events_block_change.js'; +import {BlockCreate, BlockCreateJson} from './events_block_create.js'; +import {BlockDelete, BlockDeleteJson} from './events_block_delete.js'; +import {BlockDrag, BlockDragJson} from './events_block_drag.js'; +import {BlockMove, BlockMoveJson} from './events_block_move.js'; +import {BubbleOpen, BubbleOpenJson, BubbleType} from './events_bubble_open.js'; +import {Click, ClickJson, ClickTarget} from './events_click.js'; +import {CommentBase, CommentBaseJson} from './events_comment_base.js'; +import {CommentChange, CommentChangeJson} from './events_comment_change.js'; +import {CommentCreate, CommentCreateJson} from './events_comment_create.js'; import {CommentDelete} from './events_comment_delete.js'; -import {CommentMove} from './events_comment_move.js'; -import {MarkerMove} from './events_marker_move.js'; -import {Selected} from './events_selected.js'; -import {ThemeChange} from './events_theme_change.js'; -import {ToolboxItemSelect} from './events_toolbox_item_select.js'; -import {TrashcanOpen} from './events_trashcan_open.js'; +import {CommentMove, CommentMoveJson} from './events_comment_move.js'; +import {MarkerMove, MarkerMoveJson} from './events_marker_move.js'; +import {Selected, SelectedJson} from './events_selected.js'; +import {ThemeChange, ThemeChangeJson} from './events_theme_change.js'; +import {ToolboxItemSelect, ToolboxItemSelectJson} from './events_toolbox_item_select.js'; +import {TrashcanOpen, TrashcanOpenJson} from './events_trashcan_open.js'; import {Ui} from './events_ui.js'; import {UiBase} from './events_ui_base.js'; -import {VarBase} from './events_var_base.js'; -import {VarCreate} from './events_var_create.js'; -import {VarDelete} from './events_var_delete.js'; -import {VarRename} from './events_var_rename.js'; -import {ViewportChange} from './events_viewport.js'; +import {VarBase, VarBaseJson} from './events_var_base.js'; +import {VarCreate, VarCreateJson} from './events_var_create.js'; +import {VarDelete, VarDeleteJson} from './events_var_delete.js'; +import {VarRename, VarRenameJson} from './events_var_rename.js'; +import {ViewportChange, ViewportChangeJson} from './events_viewport.js'; import * as eventUtils from './utils.js'; -import {FinishedLoading} from './workspace_events.js'; +import {FinishedLoading, FinishedLoadingJson} from './workspace_events.js'; // Events. export const Abstract = AbstractEvent; +export {AbstractEventJson}; export {BubbleOpen}; +export {BubbleOpenJson}; +export {BubbleType}; export {BlockBase}; +export {BlockBaseJson}; export {BlockChange}; +export {BlockChangeJson}; export {BlockCreate}; +export {BlockCreateJson}; export {BlockDelete}; +export {BlockDeleteJson}; export {BlockDrag}; +export {BlockDragJson}; export {BlockMove}; +export {BlockMoveJson}; export {Click}; +export {ClickJson}; +export {ClickTarget}; export {CommentBase}; +export {CommentBaseJson}; export {CommentChange}; +export {CommentChangeJson}; export {CommentCreate}; +export {CommentCreateJson}; export {CommentDelete}; export {CommentMove}; +export {CommentMoveJson}; export {FinishedLoading}; +export {FinishedLoadingJson}; export {MarkerMove}; +export {MarkerMoveJson}; export {Selected}; +export {SelectedJson}; export {ThemeChange}; +export {ThemeChangeJson}; export {ToolboxItemSelect}; +export {ToolboxItemSelectJson}; export {TrashcanOpen}; +export {TrashcanOpenJson}; export {Ui}; export {UiBase}; export {VarBase}; +export {VarBaseJson}; export {VarCreate}; +export {VarCreateJson}; export {VarDelete}; +export {VarDeleteJson}; export {VarRename}; +export {VarRenameJson}; export {ViewportChange}; +export {ViewportChangeJson}; // Event types. export const BLOCK_CHANGE = eventUtils.BLOCK_CHANGE; diff --git a/core/events/events_abstract.ts b/core/events/events_abstract.ts index f7b5731ad..78e4bd8b7 100644 --- a/core/events/events_abstract.ts +++ b/core/events/events_abstract.ts @@ -26,7 +26,7 @@ import * as eventUtils from './utils.js'; */ export abstract class Abstract { /** Whether or not the event is blank (to be populated by fromJson). */ - isBlank: boolean|null = null; + abstract isBlank: boolean; /** The workspace identifier for this event. */ workspaceId?: string = undefined; @@ -37,7 +37,7 @@ export abstract class Abstract { isUiEvent = false; /** Type of this event. */ - type?: string = undefined; + type = ''; /** @alias Blockly.Events.Abstract */ constructor() { @@ -57,12 +57,11 @@ export abstract class Abstract { * * @returns JSON representation. */ - toJson(): AnyDuringMigration { - const json = {'type': this.type}; - if (this.group) { - (json as AnyDuringMigration)['group'] = this.group; - } - return json; + toJson(): AbstractEventJson { + return { + 'type': this.type, + 'group': this.group, + }; } /** @@ -70,9 +69,9 @@ export abstract class Abstract { * * @param json JSON representation. */ - fromJson(json: AnyDuringMigration) { + fromJson(json: AbstractEventJson) { this.isBlank = false; - this.group = json['group']; + this.group = json['group'] || ''; } /** @@ -112,3 +111,8 @@ export abstract class Abstract { return workspace; } } + +export interface AbstractEventJson { + type: string; + group: string; +} diff --git a/core/events/events_block_base.ts b/core/events/events_block_base.ts index 079acd182..c5bbbe0a3 100644 --- a/core/events/events_block_base.ts +++ b/core/events/events_block_base.ts @@ -14,7 +14,7 @@ goog.declareModuleId('Blockly.Events.BlockBase'); import type {Block} from '../block.js'; -import {Abstract as AbstractEvent} from './events_abstract.js'; +import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js'; /** @@ -23,9 +23,8 @@ import {Abstract as AbstractEvent} from './events_abstract.js'; * @alias Blockly.Events.BlockBase */ export class BlockBase extends AbstractEvent { - override isBlank: AnyDuringMigration; - blockId: string; - override workspaceId: string; + override isBlank = true; + blockId?: string; /** * @param opt_block The block this event corresponds to. @@ -33,13 +32,15 @@ export class BlockBase extends AbstractEvent { */ constructor(opt_block?: Block) { super(); - this.isBlank = typeof opt_block === 'undefined'; + this.isBlank = !!opt_block; + + if (!opt_block) return; /** The block ID for the block this event pertains to */ - this.blockId = this.isBlank ? '' : opt_block!.id; + this.blockId = opt_block.id; /** The workspace identifier for this event. */ - this.workspaceId = this.isBlank ? '' : opt_block!.workspace.id; + this.workspaceId = opt_block.workspace.id; } /** @@ -47,8 +48,13 @@ export class BlockBase extends AbstractEvent { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); + override toJson(): AbstractEventJson { + const json = super.toJson() as BlockBaseJson; + if (!this.blockId) { + throw new Error( + 'The block ID is undefined. Either pass a block to ' + + 'the constructor, or call fromJson'); + } json['blockId'] = this.blockId; return json; } @@ -58,8 +64,12 @@ export class BlockBase extends AbstractEvent { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: BlockBaseJson) { super.fromJson(json); this.blockId = json['blockId']; } } + +export interface BlockBaseJson extends AbstractEventJson { + blockId: string; +} diff --git a/core/events/events_block_change.ts b/core/events/events_block_change.ts index ddf2762f1..a3cd048a1 100644 --- a/core/events/events_block_change.ts +++ b/core/events/events_block_change.ts @@ -17,7 +17,7 @@ import type {BlockSvg} from '../block_svg.js'; import * as registry from '../registry.js'; import * as Xml from '../xml.js'; -import {BlockBase} from './events_block_base.js'; +import {BlockBase, BlockBaseJson} from './events_block_base.js'; import * as eventUtils from './utils.js'; @@ -27,13 +27,11 @@ import * as eventUtils from './utils.js'; * @alias Blockly.Events.BlockChange */ export class BlockChange extends BlockBase { - override type: string; - // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. - element!: string; - // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. - name!: string|null; - oldValue: AnyDuringMigration; - newValue: AnyDuringMigration; + override type = eventUtils.BLOCK_CHANGE; + element?: string; + name?: string; + oldValue: unknown; + newValue: unknown; /** * @param opt_block The changed block. Undefined for a blank event. @@ -44,19 +42,16 @@ export class BlockChange extends BlockBase { */ constructor( opt_block?: Block, opt_element?: string, opt_name?: string|null, - opt_oldValue?: AnyDuringMigration, opt_newValue?: AnyDuringMigration) { + opt_oldValue?: unknown, opt_newValue?: unknown) { super(opt_block); - /** Type of this event. */ - this.type = eventUtils.BLOCK_CHANGE; - if (!opt_block) { return; // Blank event to be populated by fromJson. } - this.element = typeof opt_element === 'undefined' ? '' : opt_element; - this.name = typeof opt_name === 'undefined' ? '' : opt_name; - this.oldValue = typeof opt_oldValue === 'undefined' ? '' : opt_oldValue; - this.newValue = typeof opt_newValue === 'undefined' ? '' : opt_newValue; + this.element = opt_element; + this.name = opt_name || undefined; + this.oldValue = opt_oldValue; + this.newValue = opt_newValue; } /** @@ -64,12 +59,15 @@ export class BlockChange extends BlockBase { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); - json['element'] = this.element; - if (this.name) { - json['name'] = this.name; + override toJson(): BlockChangeJson { + const json = super.toJson() as BlockChangeJson; + if (!this.element) { + throw new Error( + 'The changed element is undefined. Either pass an ' + + 'element to the constructor, or call fromJson'); } + json['element'] = this.element; + json['name'] = this.name; json['oldValue'] = this.oldValue; json['newValue'] = this.newValue; return json; @@ -80,7 +78,7 @@ export class BlockChange extends BlockBase { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: BlockChangeJson) { super.fromJson(json); this.element = json['element']; this.name = json['name']; @@ -104,10 +102,16 @@ export class BlockChange extends BlockBase { */ override run(forward: boolean) { const workspace = this.getEventWorkspace_(); + if (!this.blockId) { + throw new Error( + 'The block ID is undefined. Either pass a block to ' + + 'the constructor, or call fromJson'); + } const block = workspace.getBlockById(this.blockId); if (!block) { - console.warn('Can\'t change non-existent block: ' + this.blockId); - return; + throw new Error( + 'The associated block is undefined. Either pass a ' + + 'block to the constructor, or call fromJson'); } // Assume the block is rendered so that then we can check. const blockSvg = block as BlockSvg; @@ -176,4 +180,11 @@ export class BlockChange extends BlockBase { } } +export interface BlockChangeJson extends BlockBaseJson { + element: string; + name?: string; + newValue: unknown; + oldValue: unknown; +} + registry.register(registry.Type.EVENT, eventUtils.CHANGE, BlockChange); diff --git a/core/events/events_block_create.ts b/core/events/events_block_create.ts index b433560e2..744939a88 100644 --- a/core/events/events_block_create.ts +++ b/core/events/events_block_create.ts @@ -17,7 +17,7 @@ import * as registry from '../registry.js'; import * as blocks from '../serialization/blocks.js'; import * as Xml from '../xml.js'; -import {BlockBase} from './events_block_base.js'; +import {BlockBase, BlockBaseJson} from './events_block_base.js'; import * as eventUtils from './utils.js'; @@ -27,20 +27,15 @@ import * as eventUtils from './utils.js'; * @alias Blockly.Events.BlockCreate */ export class BlockCreate extends BlockBase { - override type: string; - xml: AnyDuringMigration; - // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. - ids!: string[]; - // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. - json!: blocks.State; + override type = eventUtils.BLOCK_CREATE; + xml?: Element|DocumentFragment; + ids?: string[]; + json?: blocks.State; /** @param opt_block The created block. Undefined for a blank event. */ constructor(opt_block?: Block) { super(opt_block); - /** Type of this event. */ - this.type = eventUtils.BLOCK_CREATE; - if (!opt_block) { return; // Blank event to be populated by fromJson. } @@ -62,8 +57,23 @@ export class BlockCreate extends BlockBase { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); + override toJson(): BlockCreateJson { + const json = super.toJson() as BlockCreateJson; + if (!this.xml) { + throw new Error( + 'The block XML is undefined. Either pass a block to ' + + 'the constructor, or call fromJson'); + } + if (!this.ids) { + throw new Error( + 'The block IDs are undefined. Either pass a block to ' + + 'the constructor, or call fromJson'); + } + if (!this.json) { + throw new Error( + 'The block JSON is undefined. Either pass a block to ' + + 'the constructor, or call fromJson'); + } json['xml'] = Xml.domToText(this.xml); json['ids'] = this.ids; json['json'] = this.json; @@ -78,7 +88,7 @@ export class BlockCreate extends BlockBase { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: BlockCreateJson) { super.fromJson(json); this.xml = Xml.textToDom(json['xml']); this.ids = json['ids']; @@ -95,6 +105,16 @@ export class BlockCreate extends BlockBase { */ override run(forward: boolean) { const workspace = this.getEventWorkspace_(); + if (!this.json) { + throw new Error( + 'The block JSON is undefined. Either pass a block to ' + + 'the constructor, or call fromJson'); + } + if (!this.ids) { + throw new Error( + 'The block IDs are undefined. Either pass a block to ' + + 'the constructor, or call fromJson'); + } if (forward) { blocks.append(this.json, workspace); } else { @@ -112,4 +132,11 @@ export class BlockCreate extends BlockBase { } } +export interface BlockCreateJson extends BlockBaseJson { + xml: string; + ids: string[]; + json: object; + recordUndo?: boolean; +} + registry.register(registry.Type.EVENT, eventUtils.CREATE, BlockCreate); diff --git a/core/events/events_block_delete.ts b/core/events/events_block_delete.ts index b7a1f6f07..6a10ae044 100644 --- a/core/events/events_block_delete.ts +++ b/core/events/events_block_delete.ts @@ -17,7 +17,7 @@ import * as registry from '../registry.js'; import * as blocks from '../serialization/blocks.js'; import * as Xml from '../xml.js'; -import {BlockBase} from './events_block_base.js'; +import {BlockBase, BlockBaseJson} from './events_block_base.js'; import * as eventUtils from './utils.js'; @@ -27,22 +27,16 @@ import * as eventUtils from './utils.js'; * @alias Blockly.Events.BlockDelete */ export class BlockDelete extends BlockBase { - override type: string; - oldXml: AnyDuringMigration; - // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. - ids!: string[]; - // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. - wasShadow!: boolean; - // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. - oldJson!: blocks.State; + oldXml?: Element|DocumentFragment; + ids?: string[]; + wasShadow?: boolean; + oldJson?: blocks.State; + override type = eventUtils.BLOCK_DELETE; /** @param opt_block The deleted block. Undefined for a blank event. */ constructor(opt_block?: Block) { super(opt_block); - /** Type of this event. */ - this.type = eventUtils.BLOCK_DELETE; - if (!opt_block) { return; // Blank event to be populated by fromJson. } @@ -71,8 +65,28 @@ export class BlockDelete extends BlockBase { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); + override toJson(): BlockDeleteJson { + const json = super.toJson() as BlockDeleteJson; + if (!this.oldXml) { + throw new Error( + 'The old block XML is undefined. Either pass a block ' + + 'to the constructor, or call fromJson'); + } + if (!this.ids) { + throw new Error( + 'The block IDs are undefined. Either pass a block to ' + + 'the constructor, or call fromJson'); + } + if (this.wasShadow === undefined) { + throw new Error( + 'Whether the block was a shadow is undefined. Either ' + + 'pass a block to the constructor, or call fromJson'); + } + if (!this.oldJson) { + throw new Error( + 'The old block JSON is undefined. Either pass a block ' + + 'to the constructor, or call fromJson'); + } json['oldXml'] = Xml.domToText(this.oldXml); json['ids'] = this.ids; json['wasShadow'] = this.wasShadow; @@ -88,13 +102,13 @@ export class BlockDelete extends BlockBase { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: BlockDeleteJson) { super.fromJson(json); this.oldXml = Xml.textToDom(json['oldXml']); this.ids = json['ids']; this.wasShadow = json['wasShadow'] || this.oldXml.tagName.toLowerCase() === 'shadow'; - this.oldJson = json['oldJson'] as blocks.State; + this.oldJson = json['oldJson']; if (json['recordUndo'] !== undefined) { this.recordUndo = json['recordUndo']; } @@ -107,6 +121,16 @@ export class BlockDelete extends BlockBase { */ override run(forward: boolean) { const workspace = this.getEventWorkspace_(); + if (!this.ids) { + throw new Error( + 'The block IDs are undefined. Either pass a block to ' + + 'the constructor, or call fromJson'); + } + if (!this.oldJson) { + throw new Error( + 'The old block JSON is undefined. Either pass a block ' + + 'to the constructor, or call fromJson'); + } if (forward) { for (let i = 0; i < this.ids.length; i++) { const id = this.ids[i]; @@ -124,4 +148,12 @@ export class BlockDelete extends BlockBase { } } +export interface BlockDeleteJson extends BlockBaseJson { + oldXml: string; + ids: string[]; + wasShadow: boolean; + oldJson: blocks.State; + recordUndo?: boolean; +} + registry.register(registry.Type.EVENT, eventUtils.DELETE, BlockDelete); diff --git a/core/events/events_block_drag.ts b/core/events/events_block_drag.ts index 41b9a3bf1..5d7943527 100644 --- a/core/events/events_block_drag.ts +++ b/core/events/events_block_drag.ts @@ -14,7 +14,7 @@ goog.declareModuleId('Blockly.Events.BlockDrag'); import type {Block} from '../block.js'; import * as registry from '../registry.js'; - +import {AbstractEventJson} from './events_abstract.js'; import {UiBase} from './events_ui_base.js'; import * as eventUtils from './utils.js'; @@ -25,10 +25,10 @@ import * as eventUtils from './utils.js'; * @alias Blockly.Events.BlockDrag */ export class BlockDrag extends UiBase { - blockId: AnyDuringMigration; + blockId?: string; isStart?: boolean; blocks?: Block[]; - override type: string; + override type = eventUtils.BLOCK_DRAG; /** * @param opt_block The top block in the stack that is being dragged. @@ -41,16 +41,15 @@ export class BlockDrag extends UiBase { constructor(opt_block?: Block, opt_isStart?: boolean, opt_blocks?: Block[]) { const workspaceId = opt_block ? opt_block.workspace.id : undefined; super(workspaceId); - this.blockId = opt_block ? opt_block.id : null; + if (!opt_block) return; + + this.blockId = opt_block.id; /** Whether this is the start of a block drag. */ this.isStart = opt_isStart; /** The blocks affected by this drag event. */ this.blocks = opt_blocks; - - /** Type of this event. */ - this.type = eventUtils.BLOCK_DRAG; } /** @@ -58,10 +57,23 @@ export class BlockDrag extends UiBase { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); + override toJson(): BlockDragJson { + const json = super.toJson() as BlockDragJson; + if (this.isStart === undefined) { + throw new Error( + 'Whether this event is the start of a drag is ' + + 'undefined. Either pass the value to the constructor, or call ' + + 'fromJson'); + } + if (this.blockId === undefined) { + throw new Error( + 'The block ID is undefined. Either pass a block to ' + + 'the constructor, or call fromJson'); + } json['isStart'] = this.isStart; json['blockId'] = this.blockId; + // TODO: I don't think we should actually apply the blocks array to the JSON + // object b/c they have functions and aren't actually serializable. json['blocks'] = this.blocks; return json; } @@ -71,7 +83,7 @@ export class BlockDrag extends UiBase { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: BlockDragJson) { super.fromJson(json); this.isStart = json['isStart']; this.blockId = json['blockId']; @@ -79,4 +91,10 @@ export class BlockDrag extends UiBase { } } +export interface BlockDragJson extends AbstractEventJson { + isStart: boolean; + blockId: string; + blocks?: Block[]; +} + registry.register(registry.Type.EVENT, eventUtils.BLOCK_DRAG, BlockDrag); diff --git a/core/events/events_block_move.ts b/core/events/events_block_move.ts index 995f4c416..6cddb50dd 100644 --- a/core/events/events_block_move.ts +++ b/core/events/events_block_move.ts @@ -17,15 +17,15 @@ import {ConnectionType} from '../connection_type.js'; import * as registry from '../registry.js'; import {Coordinate} from '../utils/coordinate.js'; -import {BlockBase} from './events_block_base.js'; +import {BlockBase, BlockBaseJson} from './events_block_base.js'; import * as eventUtils from './utils.js'; interface BlockLocation { - parentId: string; - inputName: string; - coordinate: Coordinate|null; -} // eslint-disable-line no-unused-vars + parentId?: string; + inputName?: string; + coordinate?: Coordinate; +} /** * Class for a block move event. Created before the move. @@ -33,25 +33,19 @@ interface BlockLocation { * @alias Blockly.Events.BlockMove */ export class BlockMove extends BlockBase { - override type: string; - // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. - oldParentId!: string; - // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. - oldInputName!: string; - // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. - oldCoordinate!: Coordinate|null; + override type = eventUtils.BLOCK_MOVE; + oldParentId?: string; + oldInputName?: string; + oldCoordinate?: Coordinate; - newParentId: string|null = null; - newInputName: string|null = null; - newCoordinate: Coordinate|null = null; + newParentId?: string; + newInputName?: string; + newCoordinate?: Coordinate; /** @param opt_block The moved block. Undefined for a blank event. */ constructor(opt_block?: Block) { super(opt_block); - /** Type of this event. */ - this.type = eventUtils.BLOCK_MOVE; - if (!opt_block) { return; } @@ -72,17 +66,13 @@ export class BlockMove extends BlockBase { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); - if (this.newParentId) { - json['newParentId'] = this.newParentId; - } - if (this.newInputName) { - json['newInputName'] = this.newInputName; - } + override toJson(): BlockMoveJson { + const json = super.toJson() as BlockMoveJson; + json['newParentId'] = this.newParentId; + json['newInputName'] = this.newInputName; if (this.newCoordinate) { - json['newCoordinate'] = Math.round(this.newCoordinate.x) + ',' + - Math.round(this.newCoordinate.y); + json['newCoordinate'] = `${Math.round(this.newCoordinate.x)}, ` + + `${Math.round(this.newCoordinate.y)}`; } if (!this.recordUndo) { json['recordUndo'] = this.recordUndo; @@ -95,7 +85,7 @@ export class BlockMove extends BlockBase { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: BlockMoveJson) { super.fromJson(json); this.newParentId = json['newParentId']; this.newInputName = json['newInputName']; @@ -124,19 +114,27 @@ export class BlockMove extends BlockBase { */ private currentLocation_(): BlockLocation { const workspace = this.getEventWorkspace_(); + if (!this.blockId) { + throw new Error( + 'The block ID is undefined. Either pass a block to ' + + 'the constructor, or call fromJson'); + } const block = workspace.getBlockById(this.blockId); + if (!block) { + throw new Error( + 'The block associated with the block move event ' + + 'could not be found'); + } const location = {} as BlockLocation; - const parent = block!.getParent(); + const parent = block.getParent(); if (parent) { location.parentId = parent.id; - // AnyDuringMigration because: Argument of type 'Block | null' is not - // assignable to parameter of type 'Block'. - const input = parent.getInputWithBlock(block as AnyDuringMigration); + const input = parent.getInputWithBlock(block); if (input) { location.inputName = input.name; } } else { - location.coordinate = block!.getRelativeToSurfaceXY(); + location.coordinate = block.getRelativeToSurfaceXY(); } return location; } @@ -159,6 +157,11 @@ export class BlockMove extends BlockBase { */ override run(forward: boolean) { const workspace = this.getEventWorkspace_(); + if (!this.blockId) { + throw new Error( + 'The block ID is undefined. Either pass a block to ' + + 'the constructor, or call fromJson'); + } const block = workspace.getBlockById(this.blockId); if (!block) { console.warn('Can\'t move non-existent block: ' + this.blockId); @@ -206,4 +209,11 @@ export class BlockMove extends BlockBase { } } +export interface BlockMoveJson extends BlockBaseJson { + newParentId?: string; + newInputName?: string; + newCoordinate?: string; + recordUndo?: boolean; +} + registry.register(registry.Type.EVENT, eventUtils.MOVE, BlockMove); diff --git a/core/events/events_bubble_open.ts b/core/events/events_bubble_open.ts index 8e1e7d080..4768aeb6d 100644 --- a/core/events/events_bubble_open.ts +++ b/core/events/events_bubble_open.ts @@ -12,9 +12,9 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.BubbleOpen'); +import type {AbstractEventJson} from './events_abstract.js'; import type {BlockSvg} from '../block_svg.js'; import * as registry from '../registry.js'; - import {UiBase} from './events_ui_base.js'; import * as eventUtils from './utils.js'; @@ -25,10 +25,10 @@ import * as eventUtils from './utils.js'; * @alias Blockly.Events.BubbleOpen */ export class BubbleOpen extends UiBase { - blockId: string|null; + blockId?: string; isOpen?: boolean; - bubbleType?: string; - override type: string; + bubbleType?: BubbleType; + override type = eventUtils.BUBBLE_OPEN; /** * @param opt_block The associated block. Undefined for a blank event. @@ -38,19 +38,18 @@ export class BubbleOpen extends UiBase { * 'warning'. Undefined for a blank event. */ constructor( - opt_block?: BlockSvg, opt_isOpen?: boolean, opt_bubbleType?: string) { + opt_block?: BlockSvg, opt_isOpen?: boolean, opt_bubbleType?: BubbleType) { const workspaceId = opt_block ? opt_block.workspace.id : undefined; super(workspaceId); - this.blockId = opt_block ? opt_block.id : null; + if (!opt_block) return; + + this.blockId = opt_block.id; /** Whether the bubble is opening (false if closing). */ this.isOpen = opt_isOpen; /** The type of bubble. One of 'mutator', 'comment', or 'warning'. */ this.bubbleType = opt_bubbleType; - - /** Type of this event. */ - this.type = eventUtils.BUBBLE_OPEN; } /** @@ -58,11 +57,23 @@ export class BubbleOpen extends UiBase { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); + override toJson(): BubbleOpenJson { + const json = super.toJson() as BubbleOpenJson; + if (this.isOpen === undefined) { + throw new Error( + 'Whether this event is for opening the bubble is ' + + 'undefined. Either pass the value to the constructor, or call ' + + 'fromJson'); + } + if (!this.bubbleType) { + throw new Error( + 'The type of bubble is undefined. Either pass the ' + + 'value to the constructor, or call ' + + 'fromJson'); + } json['isOpen'] = this.isOpen; json['bubbleType'] = this.bubbleType; - json['blockId'] = this.blockId; + json['blockId'] = this.blockId || ''; return json; } @@ -71,7 +82,7 @@ export class BubbleOpen extends UiBase { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: BubbleOpenJson) { super.fromJson(json); this.isOpen = json['isOpen']; this.bubbleType = json['bubbleType']; @@ -79,4 +90,16 @@ export class BubbleOpen extends UiBase { } } +export enum BubbleType { + MUTATOR = 'mutator', + COMMENT = 'comment', + WARNING = 'warning', +} + +export interface BubbleOpenJson extends AbstractEventJson { + isOpen: boolean; + bubbleType: BubbleType; + blockId: string; +} + registry.register(registry.Type.EVENT, eventUtils.BUBBLE_OPEN, BubbleOpen); diff --git a/core/events/events_click.ts b/core/events/events_click.ts index 29262b2c6..c4c782a06 100644 --- a/core/events/events_click.ts +++ b/core/events/events_click.ts @@ -14,6 +14,7 @@ goog.declareModuleId('Blockly.Events.Click'); import type {Block} from '../block.js'; import * as registry from '../registry.js'; +import {AbstractEventJson} from './events_abstract.js'; import {UiBase} from './events_ui_base.js'; import * as eventUtils from './utils.js'; @@ -25,9 +26,9 @@ import * as eventUtils from './utils.js'; * @alias Blockly.Events.Click */ export class Click extends UiBase { - blockId: AnyDuringMigration; - targetType?: string; - override type: string; + blockId?: string; + targetType?: ClickTarget; + override type = eventUtils.CLICK; /** * @param opt_block The affected block. Null for click events that do not have @@ -40,19 +41,17 @@ export class Click extends UiBase { */ constructor( opt_block?: Block|null, opt_workspaceId?: string|null, - opt_targetType?: string) { + opt_targetType?: ClickTarget) { let workspaceId = opt_block ? opt_block.workspace.id : opt_workspaceId; if (workspaceId === null) { workspaceId = undefined; } super(workspaceId); - this.blockId = opt_block ? opt_block.id : null; + + this.blockId = opt_block ? opt_block.id : undefined; /** The type of element targeted by this click event. */ this.targetType = opt_targetType; - - /** Type of this event. */ - this.type = eventUtils.CLICK; } /** @@ -60,12 +59,15 @@ export class Click extends UiBase { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); - json['targetType'] = this.targetType; - if (this.blockId) { - json['blockId'] = this.blockId; + override toJson(): ClickJson { + const json = super.toJson() as ClickJson; + if (!this.targetType) { + throw new Error( + 'The click target type is undefined. Either pass a block to ' + + 'the constructor, or call fromJson'); } + json['targetType'] = this.targetType; + json['blockId'] = this.blockId; return json; } @@ -74,11 +76,22 @@ export class Click extends UiBase { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: ClickJson) { super.fromJson(json); this.targetType = json['targetType']; this.blockId = json['blockId']; } } +export enum ClickTarget { + BLOCK = 'block', + WORKSPACE = 'workspace', + ZOOM_CONTROLS = 'zoom_controls', +} + +export interface ClickJson extends AbstractEventJson { + targetType: ClickTarget; + blockId?: string; +} + registry.register(registry.Type.EVENT, eventUtils.CLICK, Click); diff --git a/core/events/events_comment_base.ts b/core/events/events_comment_base.ts index 7d8d39268..6329e3902 100644 --- a/core/events/events_comment_base.ts +++ b/core/events/events_comment_base.ts @@ -16,7 +16,7 @@ import * as utilsXml from '../utils/xml.js'; import type {WorkspaceComment} from '../workspace_comment.js'; import * as Xml from '../xml.js'; -import {Abstract as AbstractEvent} from './events_abstract.js'; +import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js'; import type {CommentCreate} from './events_comment_create.js'; import type {CommentDelete} from './events_comment_delete.js'; import * as eventUtils from './utils.js'; @@ -28,9 +28,8 @@ import * as eventUtils from './utils.js'; * @alias Blockly.Events.CommentBase */ export class CommentBase extends AbstractEvent { - override isBlank: boolean; - commentId: string; - override workspaceId: string; + override isBlank = true; + commentId?: string; /** * @param opt_comment The comment this event corresponds to. Undefined for a @@ -39,13 +38,15 @@ export class CommentBase extends AbstractEvent { constructor(opt_comment?: WorkspaceComment) { super(); /** Whether or not an event is blank. */ - this.isBlank = typeof opt_comment === 'undefined'; + this.isBlank = !opt_comment; + + if (!opt_comment) return; /** The ID of the comment this event pertains to. */ - this.commentId = this.isBlank ? '' : opt_comment!.id; + this.commentId = opt_comment.id; /** The workspace identifier for this event. */ - this.workspaceId = this.isBlank ? '' : opt_comment!.workspace.id; + this.workspaceId = opt_comment.workspace.id; /** * The event group ID for the group this event belongs to. Groups define @@ -63,11 +64,14 @@ export class CommentBase extends AbstractEvent { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); - if (this.commentId) { - json['commentId'] = this.commentId; + override toJson(): CommentBaseJson { + const json = super.toJson() as CommentBaseJson; + if (!this.commentId) { + throw new Error( + 'The comment ID is undefined. Either pass a comment to ' + + 'the constructor, or call fromJson'); } + json['commentId'] = this.commentId; return json; } @@ -76,7 +80,7 @@ export class CommentBase extends AbstractEvent { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: CommentBaseJson) { super.fromJson(json); this.commentId = json['commentId']; } @@ -92,9 +96,17 @@ export class CommentBase extends AbstractEvent { const workspace = event.getEventWorkspace_(); if (create) { const xmlElement = utilsXml.createElement('xml'); + if (!event.xml) { + throw new Error('Ecountered a comment event without proper xml'); + } xmlElement.appendChild(event.xml); Xml.domToWorkspace(xmlElement, workspace); } else { + if (!event.commentId) { + throw new Error( + 'The comment ID is undefined. Either pass a comment to ' + + 'the constructor, or call fromJson'); + } const comment = workspace.getCommentById(event.commentId); if (comment) { comment.dispose(); @@ -106,3 +118,7 @@ export class CommentBase extends AbstractEvent { } } } + +export interface CommentBaseJson extends AbstractEventJson { + commentId: string; +} diff --git a/core/events/events_comment_change.ts b/core/events/events_comment_change.ts index ec98eb312..134b23714 100644 --- a/core/events/events_comment_change.ts +++ b/core/events/events_comment_change.ts @@ -15,7 +15,7 @@ goog.declareModuleId('Blockly.Events.CommentChange'); import * as registry from '../registry.js'; import type {WorkspaceComment} from '../workspace_comment.js'; -import {CommentBase} from './events_comment_base.js'; +import {CommentBase, CommentBaseJson} from './events_comment_base.js'; import * as eventUtils from './utils.js'; @@ -25,12 +25,9 @@ import * as eventUtils from './utils.js'; * @alias Blockly.Events.CommentChange */ export class CommentChange extends CommentBase { - override type: string; - - // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. - oldContents_!: string; - // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. - newContents_!: string; + override type = eventUtils.COMMENT_CHANGE; + oldContents_?: string; + newContents_?: string; /** * @param opt_comment The comment that is being changed. Undefined for a @@ -43,9 +40,6 @@ export class CommentChange extends CommentBase { opt_newContents?: string) { super(opt_comment); - /** Type of this event. */ - this.type = eventUtils.COMMENT_CHANGE; - if (!opt_comment) { return; // Blank event to be populated by fromJson. } @@ -61,8 +55,18 @@ export class CommentChange extends CommentBase { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); + override toJson(): CommentChangeJson { + const json = super.toJson() as CommentChangeJson; + if (!this.oldContents_) { + throw new Error( + 'The old contents is undefined. Either pass a value to ' + + 'the constructor, or call fromJson'); + } + if (!this.newContents_) { + throw new Error( + 'The new contents is undefined. Either pass a value to ' + + 'the constructor, or call fromJson'); + } json['oldContents'] = this.oldContents_; json['newContents'] = this.newContents_; return json; @@ -73,7 +77,7 @@ export class CommentChange extends CommentBase { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: CommentChangeJson) { super.fromJson(json); this.oldContents_ = json['oldContents']; this.newContents_ = json['newContents']; @@ -95,16 +99,35 @@ export class CommentChange extends CommentBase { */ override run(forward: boolean) { const workspace = this.getEventWorkspace_(); + if (!this.commentId) { + throw new Error( + 'The comment ID is undefined. Either pass a comment to ' + + 'the constructor, or call fromJson'); + } const comment = workspace.getCommentById(this.commentId); if (!comment) { console.warn('Can\'t change non-existent comment: ' + this.commentId); return; } const contents = forward ? this.newContents_ : this.oldContents_; - + if (!contents) { + if (forward) { + throw new Error( + 'The new contents is undefined. Either pass a value to ' + + 'the constructor, or call fromJson'); + } + throw new Error( + 'The old contents is undefined. Either pass a value to ' + + 'the constructor, or call fromJson'); + } comment.setContent(contents); } } +export interface CommentChangeJson extends CommentBaseJson { + oldContents: string; + newContents: string; +} + registry.register( registry.Type.EVENT, eventUtils.COMMENT_CHANGE, CommentChange); diff --git a/core/events/events_comment_create.ts b/core/events/events_comment_create.ts index 55f7b0c9c..384d4cca8 100644 --- a/core/events/events_comment_create.ts +++ b/core/events/events_comment_create.ts @@ -16,7 +16,7 @@ import * as registry from '../registry.js'; import type {WorkspaceComment} from '../workspace_comment.js'; import * as Xml from '../xml.js'; -import {CommentBase} from './events_comment_base.js'; +import {CommentBase, CommentBaseJson} from './events_comment_base.js'; import * as eventUtils from './utils.js'; @@ -26,9 +26,9 @@ import * as eventUtils from './utils.js'; * @alias Blockly.Events.CommentCreate */ export class CommentCreate extends CommentBase { - override type: string; + override type = eventUtils.COMMENT_CREATE; - xml: AnyDuringMigration; + xml?: Element|DocumentFragment; /** * @param opt_comment The created comment. @@ -37,9 +37,6 @@ export class CommentCreate extends CommentBase { constructor(opt_comment?: WorkspaceComment) { super(opt_comment); - /** Type of this event. */ - this.type = eventUtils.COMMENT_CREATE; - if (!opt_comment) { return; } @@ -53,8 +50,13 @@ export class CommentCreate extends CommentBase { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); + override toJson(): CommentCreateJson { + const json = super.toJson() as CommentCreateJson; + if (!this.xml) { + throw new Error( + 'The comment XML is undefined. Either pass a comment to ' + + 'the constructor, or call fromJson'); + } json['xml'] = Xml.domToText(this.xml); return json; } @@ -64,7 +66,7 @@ export class CommentCreate extends CommentBase { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: CommentCreateJson) { super.fromJson(json); this.xml = Xml.textToDom(json['xml']); } @@ -79,5 +81,9 @@ export class CommentCreate extends CommentBase { } } +export interface CommentCreateJson extends CommentBaseJson { + xml: string; +} + registry.register( registry.Type.EVENT, eventUtils.COMMENT_CREATE, CommentCreate); diff --git a/core/events/events_comment_delete.ts b/core/events/events_comment_delete.ts index 8f2c79138..a2c6fddf0 100644 --- a/core/events/events_comment_delete.ts +++ b/core/events/events_comment_delete.ts @@ -25,8 +25,8 @@ import * as eventUtils from './utils.js'; * @alias Blockly.Events.CommentDelete */ export class CommentDelete extends CommentBase { - override type: string; - xml: AnyDuringMigration; + override type = eventUtils.COMMENT_DELETE; + xml?: Element; /** * @param opt_comment The deleted comment. @@ -35,34 +35,12 @@ export class CommentDelete extends CommentBase { constructor(opt_comment?: WorkspaceComment) { super(opt_comment); - /** Type of this event. */ - this.type = eventUtils.COMMENT_DELETE; - if (!opt_comment) { return; // Blank event to be populated by fromJson. } this.xml = opt_comment.toXmlWithXY(); } - // TODO (#1266): "Full" and "minimal" serialization. - /** - * Encode the event as JSON. - * - * @returns JSON representation. - */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); - return json; - } - - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: AnyDuringMigration) { - super.fromJson(json); - } /** * Run a creation event. diff --git a/core/events/events_comment_move.ts b/core/events/events_comment_move.ts index 44344daa6..b9cd62e1c 100644 --- a/core/events/events_comment_move.ts +++ b/core/events/events_comment_move.ts @@ -16,7 +16,7 @@ import * as registry from '../registry.js'; import {Coordinate} from '../utils/coordinate.js'; import type {WorkspaceComment} from '../workspace_comment.js'; -import {CommentBase} from './events_comment_base.js'; +import {CommentBase, CommentBaseJson} from './events_comment_base.js'; import * as eventUtils from './utils.js'; @@ -26,17 +26,11 @@ import * as eventUtils from './utils.js'; * @alias Blockly.Events.CommentMove */ export class CommentMove extends CommentBase { - override type: string; - - // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. - comment_!: WorkspaceComment; - // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. - oldCoordinate_!: Coordinate; - + override type = eventUtils.COMMENT_MOVE; + comment_?: WorkspaceComment; + oldCoordinate_?: Coordinate; /** The location after the move, in workspace coordinates. */ - // AnyDuringMigration because: Type 'null' is not assignable to type - // 'Coordinate'. - newCoordinate_: Coordinate = null as AnyDuringMigration; + newCoordinate_?: Coordinate; /** * @param opt_comment The comment that is being moved. Undefined for a blank @@ -45,16 +39,12 @@ export class CommentMove extends CommentBase { constructor(opt_comment?: WorkspaceComment) { super(opt_comment); - /** Type of this event. */ - this.type = eventUtils.COMMENT_MOVE; - if (!opt_comment) { return; // Blank event to be populated by fromJson. } /** - * The comment that is being moved. Will be cleared after recording the new - * location. + * The comment that is being moved. */ this.comment_ = opt_comment; @@ -67,15 +57,17 @@ export class CommentMove extends CommentBase { * called once. */ recordNew() { - if (!this.comment_) { + if (this.newCoordinate_) { throw Error( 'Tried to record the new position of a comment on the ' + 'same event twice.'); } + if (!this.comment_) { + throw new Error( + 'The comment is undefined. Pass a comment to ' + + 'the constructor if you want to use the record functionality'); + } this.newCoordinate_ = this.comment_.getXY(); - // AnyDuringMigration because: Type 'null' is not assignable to type - // 'WorkspaceComment'. - this.comment_ = null as AnyDuringMigration; } /** @@ -94,16 +86,22 @@ export class CommentMove extends CommentBase { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); - if (this.oldCoordinate_) { - json['oldCoordinate'] = Math.round(this.oldCoordinate_.x) + ',' + - Math.round(this.oldCoordinate_.y); + override toJson(): CommentMoveJson { + const json = super.toJson() as CommentMoveJson; + if (!this.oldCoordinate_) { + throw new Error( + 'The old comment position is undefined. Either pass a comment to ' + + 'the constructor, or call fromJson'); } - if (this.newCoordinate_) { - json['newCoordinate'] = Math.round(this.newCoordinate_.x) + ',' + - Math.round(this.newCoordinate_.y); + if (!this.newCoordinate_) { + throw new Error( + 'The new comment position is undefined. Either call recordNew, or ' + + 'call fromJson'); } + json['oldCoordinate'] = `${Math.round(this.oldCoordinate_.x)}, ` + + `${Math.round(this.oldCoordinate_.y)}`; + json['newCoordinate'] = Math.round(this.newCoordinate_.x) + ',' + + Math.round(this.newCoordinate_.y); return json; } @@ -112,17 +110,12 @@ export class CommentMove extends CommentBase { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: CommentMoveJson) { super.fromJson(json); - - if (json['oldCoordinate']) { - const xy = json['oldCoordinate'].split(','); - this.oldCoordinate_ = new Coordinate(Number(xy[0]), Number(xy[1])); - } - if (json['newCoordinate']) { - const xy = json['newCoordinate'].split(','); - this.newCoordinate_ = new Coordinate(Number(xy[0]), Number(xy[1])); - } + let xy = json['oldCoordinate'].split(','); + this.oldCoordinate_ = new Coordinate(Number(xy[0]), Number(xy[1])); + xy = json['newCoordinate'].split(','); + this.newCoordinate_ = new Coordinate(Number(xy[0]), Number(xy[1])); } /** @@ -141,6 +134,11 @@ export class CommentMove extends CommentBase { */ override run(forward: boolean) { const workspace = this.getEventWorkspace_(); + if (!this.commentId) { + throw new Error( + 'The comment ID is undefined. Either pass a comment to ' + + 'the constructor, or call fromJson'); + } const comment = workspace.getCommentById(this.commentId); if (!comment) { console.warn('Can\'t move non-existent comment: ' + this.commentId); @@ -148,10 +146,21 @@ export class CommentMove extends CommentBase { } const target = forward ? this.newCoordinate_ : this.oldCoordinate_; + if (!target) { + throw new Error( + 'Either oldCoordinate_ or newCoordinate_ is undefined. ' + + 'Either pass a comment to the constructor and call recordNew, ' + + 'or call fromJson'); + } // TODO: Check if the comment is being dragged, and give up if so. const current = comment.getXY(); comment.moveBy(target.x - current.x, target.y - current.y); } } +export interface CommentMoveJson extends CommentBaseJson { + oldCoordinate: string; + newCoordinate: string; +} + registry.register(registry.Type.EVENT, eventUtils.COMMENT_MOVE, CommentMove); diff --git a/core/events/events_marker_move.ts b/core/events/events_marker_move.ts index a3d0d21f8..c7f3aa071 100644 --- a/core/events/events_marker_move.ts +++ b/core/events/events_marker_move.ts @@ -16,6 +16,7 @@ import type {Block} from '../block.js'; 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'; @@ -27,11 +28,11 @@ import * as eventUtils from './utils.js'; * @alias Blockly.Events.MarkerMove */ export class MarkerMove extends UiBase { - blockId: string|null; - oldNode?: ASTNode|null; + blockId?: string; + oldNode?: ASTNode; newNode?: ASTNode; isCursor?: boolean; - override type: string; + override type = eventUtils.MARKER_MOVE; /** * @param opt_block The affected block. Null if current node is of type @@ -52,20 +53,17 @@ export class MarkerMove extends UiBase { } super(workspaceId); - /** The workspace identifier for this event. */ - this.blockId = opt_block ? opt_block.id : null; + /** The block identifier for this event. */ + this.blockId = opt_block?.id; /** The old node the marker used to be on. */ - this.oldNode = opt_oldNode; + this.oldNode = opt_oldNode || undefined; /** The new node the marker is now on. */ this.newNode = opt_newNode; /** Whether this is a cursor event. */ this.isCursor = isCursor; - - /** Type of this event. */ - this.type = eventUtils.MARKER_MOVE; } /** @@ -73,8 +71,18 @@ export class MarkerMove extends UiBase { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); + override toJson(): MarkerMoveJson { + const json = super.toJson() as MarkerMoveJson; + if (this.isCursor === undefined) { + throw new Error( + 'Whether this is a cursor event or not is undefined. Either pass ' + + 'a value to the constructor, or call fromJson'); + } + if (!this.newNode) { + throw new Error( + 'The new node is undefined. Either pass a node to ' + + 'the constructor, or call fromJson'); + } json['isCursor'] = this.isCursor; json['blockId'] = this.blockId; json['oldNode'] = this.oldNode; @@ -87,7 +95,7 @@ export class MarkerMove extends UiBase { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: MarkerMoveJson) { super.fromJson(json); this.isCursor = json['isCursor']; this.blockId = json['blockId']; @@ -96,4 +104,11 @@ export class MarkerMove extends UiBase { } } +export interface MarkerMoveJson extends AbstractEventJson { + isCursor: boolean; + blockId?: string; + oldNode?: ASTNode; + newNode: ASTNode; +} + registry.register(registry.Type.EVENT, eventUtils.MARKER_MOVE, MarkerMove); diff --git a/core/events/events_selected.ts b/core/events/events_selected.ts index 5b1ee99bd..1e596a1ae 100644 --- a/core/events/events_selected.ts +++ b/core/events/events_selected.ts @@ -13,6 +13,7 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('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'; @@ -24,9 +25,9 @@ import * as eventUtils from './utils.js'; * @alias Blockly.Events.Selected */ export class Selected extends UiBase { - oldElementId?: string|null; - newElementId?: string|null; - override type: string; + oldElementId?: string; + newElementId?: string; + override type = eventUtils.SELECTED; /** * @param opt_oldElementId The ID of the previously selected element. Null if @@ -41,14 +42,11 @@ export class Selected extends UiBase { opt_workspaceId?: string) { super(opt_workspaceId); - /** The ID of the last selected element. */ - this.oldElementId = opt_oldElementId; + /** The id of the last selected element. */ + this.oldElementId = opt_oldElementId ?? undefined; - /** The ID of the selected element. */ - this.newElementId = opt_newElementId; - - /** Type of this event. */ - this.type = eventUtils.SELECTED; + /** The id of the selected element. */ + this.newElementId = opt_newElementId ?? undefined; } /** @@ -56,8 +54,8 @@ export class Selected extends UiBase { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); + override toJson(): SelectedJson { + const json = super.toJson() as SelectedJson; json['oldElementId'] = this.oldElementId; json['newElementId'] = this.newElementId; return json; @@ -68,11 +66,16 @@ export class Selected extends UiBase { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: SelectedJson) { super.fromJson(json); this.oldElementId = json['oldElementId']; this.newElementId = json['newElementId']; } } +export interface SelectedJson extends AbstractEventJson { + oldElementId?: string; + newElementId?: string; +} + registry.register(registry.Type.EVENT, eventUtils.SELECTED, Selected); diff --git a/core/events/events_theme_change.ts b/core/events/events_theme_change.ts index 811b69ab0..7c47bd58e 100644 --- a/core/events/events_theme_change.ts +++ b/core/events/events_theme_change.ts @@ -13,7 +13,7 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.ThemeChange'); 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'; @@ -25,7 +25,7 @@ import * as eventUtils from './utils.js'; */ export class ThemeChange extends UiBase { themeName?: string; - override type: string; + override type = eventUtils.THEME_CHANGE; /** * @param opt_themeName The theme name. Undefined for a blank event. @@ -37,9 +37,6 @@ export class ThemeChange extends UiBase { /** The theme name. */ this.themeName = opt_themeName; - - /** Type of this event. */ - this.type = eventUtils.THEME_CHANGE; } /** @@ -47,8 +44,13 @@ export class ThemeChange extends UiBase { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); + override toJson(): ThemeChangeJson { + const json = super.toJson() as ThemeChangeJson; + if (!this.themeName) { + throw new Error( + 'The theme name is undefined. Either pass a theme name to ' + + 'the constructor, or call fromJson'); + } json['themeName'] = this.themeName; return json; } @@ -58,10 +60,14 @@ export class ThemeChange extends UiBase { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: ThemeChangeJson) { super.fromJson(json); this.themeName = json['themeName']; } } +export interface ThemeChangeJson extends AbstractEventJson { + themeName: string; +} + registry.register(registry.Type.EVENT, eventUtils.THEME_CHANGE, ThemeChange); diff --git a/core/events/events_toolbox_item_select.ts b/core/events/events_toolbox_item_select.ts index 6983d5886..39a13eefc 100644 --- a/core/events/events_toolbox_item_select.ts +++ b/core/events/events_toolbox_item_select.ts @@ -13,7 +13,7 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.ToolboxItemSelect'); 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'; @@ -24,9 +24,9 @@ import * as eventUtils from './utils.js'; * @alias Blockly.Events.ToolboxItemSelect */ export class ToolboxItemSelect extends UiBase { - oldItem?: string|null; - newItem?: string|null; - override type: string; + oldItem?: string; + newItem?: string; + override type = eventUtils.TOOLBOX_ITEM_SELECT; /** * @param opt_oldItem The previously selected toolbox item. @@ -42,13 +42,10 @@ export class ToolboxItemSelect extends UiBase { super(opt_workspaceId); /** The previously selected toolbox item. */ - this.oldItem = opt_oldItem; + this.oldItem = opt_oldItem ?? undefined; /** The newly selected toolbox item. */ - this.newItem = opt_newItem; - - /** Type of this event. */ - this.type = eventUtils.TOOLBOX_ITEM_SELECT; + this.newItem = opt_newItem ?? undefined; } /** @@ -56,8 +53,8 @@ export class ToolboxItemSelect extends UiBase { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); + override toJson(): ToolboxItemSelectJson { + const json = super.toJson() as ToolboxItemSelectJson; json['oldItem'] = this.oldItem; json['newItem'] = this.newItem; return json; @@ -68,12 +65,17 @@ export class ToolboxItemSelect extends UiBase { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: ToolboxItemSelectJson) { super.fromJson(json); this.oldItem = json['oldItem']; this.newItem = json['newItem']; } } +export interface ToolboxItemSelectJson extends AbstractEventJson { + oldItem?: string; + newItem?: string; +} + registry.register( registry.Type.EVENT, eventUtils.TOOLBOX_ITEM_SELECT, ToolboxItemSelect); diff --git a/core/events/events_trashcan_open.ts b/core/events/events_trashcan_open.ts index f937072f4..ff1e209af 100644 --- a/core/events/events_trashcan_open.ts +++ b/core/events/events_trashcan_open.ts @@ -13,6 +13,7 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('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'; @@ -25,7 +26,7 @@ import * as eventUtils from './utils.js'; */ export class TrashcanOpen extends UiBase { isOpen?: boolean; - override type: string; + override type = eventUtils.TRASHCAN_OPEN; /** * @param opt_isOpen Whether the trashcan flyout is opening (false if @@ -38,9 +39,6 @@ export class TrashcanOpen extends UiBase { /** Whether the trashcan flyout is opening (false if closing). */ this.isOpen = opt_isOpen; - - /** Type of this event. */ - this.type = eventUtils.TRASHCAN_OPEN; } /** @@ -48,8 +46,13 @@ export class TrashcanOpen extends UiBase { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); + override toJson(): TrashcanOpenJson { + const json = super.toJson() as TrashcanOpenJson; + if (this.isOpen === undefined) { + throw new Error( + 'Whether this is already open or not is undefined. Either pass ' + + 'a value to the constructor, or call fromJson'); + } json['isOpen'] = this.isOpen; return json; } @@ -59,10 +62,14 @@ export class TrashcanOpen extends UiBase { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: TrashcanOpenJson) { super.fromJson(json); this.isOpen = json['isOpen']; } } +export interface TrashcanOpenJson extends AbstractEventJson { + isOpen: boolean; +} + registry.register(registry.Type.EVENT, eventUtils.TRASHCAN_OPEN, TrashcanOpen); diff --git a/core/events/events_ui.ts b/core/events/events_ui.ts index 38c7e82e2..670d9143d 100644 --- a/core/events/events_ui.ts +++ b/core/events/events_ui.ts @@ -15,7 +15,6 @@ goog.declareModuleId('Blockly.Events.Ui'); import type {Block} from '../block.js'; import * as registry from '../registry.js'; - import {UiBase} from './events_ui_base.js'; import * as eventUtils from './utils.js'; @@ -31,7 +30,7 @@ export class Ui extends UiBase { element: AnyDuringMigration; oldValue: AnyDuringMigration; newValue: AnyDuringMigration; - override type: string; + override type = eventUtils.UI; /** * @param opt_block The affected block. Null for UI events that do not have @@ -50,9 +49,6 @@ export class Ui extends UiBase { this.element = typeof opt_element === 'undefined' ? '' : opt_element; this.oldValue = typeof opt_oldValue === 'undefined' ? '' : opt_oldValue; this.newValue = typeof opt_newValue === 'undefined' ? '' : opt_newValue; - - /** Type of this event. */ - this.type = eventUtils.UI; } /** @@ -61,7 +57,7 @@ export class Ui extends UiBase { * @returns JSON representation. */ override toJson(): AnyDuringMigration { - const json = super.toJson(); + const json = super.toJson() as AnyDuringMigration; json['element'] = this.element; if (this.newValue !== undefined) { json['newValue'] = this.newValue; diff --git a/core/events/events_ui_base.ts b/core/events/events_ui_base.ts index 7ac36a756..5f585f079 100644 --- a/core/events/events_ui_base.ts +++ b/core/events/events_ui_base.ts @@ -26,7 +26,7 @@ import {Abstract as AbstractEvent} from './events_abstract.js'; * @alias Blockly.Events.UiBase */ export class UiBase extends AbstractEvent { - override isBlank: boolean; + override isBlank = true; override workspaceId: string; // UI events do not undo or redo. diff --git a/core/events/events_var_base.ts b/core/events/events_var_base.ts index 6bcf1d381..e16426638 100644 --- a/core/events/events_var_base.ts +++ b/core/events/events_var_base.ts @@ -14,7 +14,7 @@ goog.declareModuleId('Blockly.Events.VarBase'); import type {VariableModel} from '../variable_model.js'; -import {Abstract as AbstractEvent} from './events_abstract.js'; +import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js'; /** @@ -23,9 +23,8 @@ import {Abstract as AbstractEvent} from './events_abstract.js'; * @alias Blockly.Events.VarBase */ export class VarBase extends AbstractEvent { - override isBlank: AnyDuringMigration; - varId: string; - override workspaceId: string; + override isBlank = true; + varId?: string; /** * @param opt_variable The variable this event corresponds to. Undefined for @@ -34,12 +33,13 @@ export class VarBase extends AbstractEvent { constructor(opt_variable?: VariableModel) { super(); this.isBlank = typeof opt_variable === 'undefined'; + if (!opt_variable) return; - /** The variable ID for the variable this event pertains to. */ - this.varId = this.isBlank ? '' : opt_variable!.getId(); + /** The variable id for the variable this event pertains to. */ + this.varId = opt_variable.getId(); /** The workspace identifier for this event. */ - this.workspaceId = this.isBlank ? '' : opt_variable!.workspace.id; + this.workspaceId = opt_variable.workspace.id; } /** @@ -47,8 +47,13 @@ export class VarBase extends AbstractEvent { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); + override toJson(): VarBaseJson { + const json = super.toJson() as VarBaseJson; + if (!this.varId) { + throw new Error( + 'The var ID is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson'); + } json['varId'] = this.varId; return json; } @@ -58,8 +63,12 @@ export class VarBase extends AbstractEvent { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: VarBaseJson) { super.fromJson(json); this.varId = json['varId']; } } + +export interface VarBaseJson extends AbstractEventJson { + varId: string; +} diff --git a/core/events/events_var_create.ts b/core/events/events_var_create.ts index 6dc0ac069..326d6c301 100644 --- a/core/events/events_var_create.ts +++ b/core/events/events_var_create.ts @@ -15,7 +15,7 @@ goog.declareModuleId('Blockly.Events.VarCreate'); import * as registry from '../registry.js'; import type {VariableModel} from '../variable_model.js'; -import {VarBase} from './events_var_base.js'; +import {VarBase, VarBaseJson} from './events_var_base.js'; import * as eventUtils from './utils.js'; @@ -25,12 +25,9 @@ import * as eventUtils from './utils.js'; * @alias Blockly.Events.VarCreate */ export class VarCreate extends VarBase { - override type: string; - - // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. - varType!: string; - // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. - varName!: string; + override type = eventUtils.VAR_CREATE; + varType?: string; + varName?: string; /** * @param opt_variable The created variable. Undefined for a blank event. @@ -38,9 +35,6 @@ export class VarCreate extends VarBase { constructor(opt_variable?: VariableModel) { super(opt_variable); - /** Type of this event. */ - this.type = eventUtils.VAR_CREATE; - if (!opt_variable) { return; // Blank event to be populated by fromJson. } @@ -53,8 +47,18 @@ export class VarCreate extends VarBase { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); + override toJson(): VarCreateJson { + const json = super.toJson() as VarCreateJson; + if (!this.varType) { + throw new Error( + 'The var type is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson'); + } + if (!this.varName) { + throw new Error( + 'The var name is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson'); + } json['varType'] = this.varType; json['varName'] = this.varName; return json; @@ -65,7 +69,7 @@ export class VarCreate extends VarBase { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: VarCreateJson) { super.fromJson(json); this.varType = json['varType']; this.varName = json['varName']; @@ -78,6 +82,16 @@ export class VarCreate extends VarBase { */ override run(forward: boolean) { const workspace = this.getEventWorkspace_(); + if (!this.varId) { + throw new Error( + 'The var ID is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson'); + } + if (!this.varName) { + throw new Error( + 'The var name is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson'); + } if (forward) { workspace.createVariable(this.varName, this.varType, this.varId); } else { @@ -86,4 +100,9 @@ export class VarCreate extends VarBase { } } +export interface VarCreateJson extends VarBaseJson { + varType: string; + varName: string; +} + registry.register(registry.Type.EVENT, eventUtils.VAR_CREATE, VarCreate); diff --git a/core/events/events_var_delete.ts b/core/events/events_var_delete.ts index 3c78e6b35..a8960ddd5 100644 --- a/core/events/events_var_delete.ts +++ b/core/events/events_var_delete.ts @@ -15,7 +15,7 @@ goog.declareModuleId('Blockly.Events.VarDelete'); import * as registry from '../registry.js'; import type {VariableModel} from '../variable_model.js'; -import {VarBase} from './events_var_base.js'; +import {VarBase, VarBaseJson} from './events_var_base.js'; import * as eventUtils from './utils.js'; @@ -25,12 +25,9 @@ import * as eventUtils from './utils.js'; * @alias Blockly.Events.VarDelete */ export class VarDelete extends VarBase { - override type: string; - - // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. - varType!: string; - // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. - varName!: string; + override type = eventUtils.VAR_DELETE; + varType?: string; + varName?: string; /** * @param opt_variable The deleted variable. Undefined for a blank event. @@ -38,9 +35,6 @@ export class VarDelete extends VarBase { constructor(opt_variable?: VariableModel) { super(opt_variable); - /** Type of this event. */ - this.type = eventUtils.VAR_DELETE; - if (!opt_variable) { return; // Blank event to be populated by fromJson. } @@ -53,8 +47,18 @@ export class VarDelete extends VarBase { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); + override toJson(): VarDeleteJson { + const json = super.toJson() as VarDeleteJson; + if (!this.varType) { + throw new Error( + 'The var type is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson'); + } + if (!this.varName) { + throw new Error( + 'The var name is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson'); + } json['varType'] = this.varType; json['varName'] = this.varName; return json; @@ -65,7 +69,7 @@ export class VarDelete extends VarBase { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: VarDeleteJson) { super.fromJson(json); this.varType = json['varType']; this.varName = json['varName']; @@ -78,6 +82,16 @@ export class VarDelete extends VarBase { */ override run(forward: boolean) { const workspace = this.getEventWorkspace_(); + if (!this.varId) { + throw new Error( + 'The var ID is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson'); + } + if (!this.varName) { + throw new Error( + 'The var name is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson'); + } if (forward) { workspace.deleteVariableById(this.varId); } else { @@ -86,4 +100,9 @@ export class VarDelete extends VarBase { } } +export interface VarDeleteJson extends VarBaseJson { + varType: string; + varName: string; +} + registry.register(registry.Type.EVENT, eventUtils.VAR_DELETE, VarDelete); diff --git a/core/events/events_var_rename.ts b/core/events/events_var_rename.ts index 0497f45aa..f3bfb489d 100644 --- a/core/events/events_var_rename.ts +++ b/core/events/events_var_rename.ts @@ -15,7 +15,7 @@ goog.declareModuleId('Blockly.Events.VarRename'); import * as registry from '../registry.js'; import type {VariableModel} from '../variable_model.js'; -import {VarBase} from './events_var_base.js'; +import {VarBase, VarBaseJson} from './events_var_base.js'; import * as eventUtils from './utils.js'; @@ -25,12 +25,9 @@ import * as eventUtils from './utils.js'; * @alias Blockly.Events.VarRename */ export class VarRename extends VarBase { - override type: string; - - // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. - oldName!: string; - // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. - newName!: string; + override type = eventUtils.VAR_RENAME; + oldName?: string; + newName?: string; /** * @param opt_variable The renamed variable. Undefined for a blank event. @@ -39,9 +36,6 @@ export class VarRename extends VarBase { constructor(opt_variable?: VariableModel, newName?: string) { super(opt_variable); - /** Type of this event. */ - this.type = eventUtils.VAR_RENAME; - if (!opt_variable) { return; // Blank event to be populated by fromJson. } @@ -54,8 +48,18 @@ export class VarRename extends VarBase { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); + override toJson(): VarRenameJson { + const json = super.toJson() as VarRenameJson; + if (!this.oldName) { + throw new Error( + 'The old var name is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson'); + } + if (!this.newName) { + throw new Error( + 'The new var name is undefined. Either pass a value to ' + + 'the constructor, or call fromJson'); + } json['oldName'] = this.oldName; json['newName'] = this.newName; return json; @@ -66,7 +70,7 @@ export class VarRename extends VarBase { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: VarRenameJson) { super.fromJson(json); this.oldName = json['oldName']; this.newName = json['newName']; @@ -79,6 +83,21 @@ export class VarRename extends VarBase { */ override run(forward: boolean) { const workspace = this.getEventWorkspace_(); + if (!this.varId) { + throw new Error( + 'The var ID is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson'); + } + if (!this.oldName) { + throw new Error( + 'The old var name is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson'); + } + if (!this.newName) { + throw new Error( + 'The new var name is undefined. Either pass a value to ' + + 'the constructor, or call fromJson'); + } if (forward) { workspace.renameVariableById(this.varId, this.newName); } else { @@ -87,4 +106,9 @@ export class VarRename extends VarBase { } } +export interface VarRenameJson extends VarBaseJson { + oldName: string; + newName: string; +} + registry.register(registry.Type.EVENT, eventUtils.VAR_RENAME, VarRename); diff --git a/core/events/events_viewport.ts b/core/events/events_viewport.ts index 11912b659..a44fc863a 100644 --- a/core/events/events_viewport.ts +++ b/core/events/events_viewport.ts @@ -13,7 +13,7 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.ViewportChange'); 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'; @@ -28,7 +28,7 @@ export class ViewportChange extends UiBase { viewLeft?: number; scale?: number; oldScale?: number; - override type: string; + override type = eventUtils.VIEWPORT_CHANGE; /** * @param opt_top Top-edge of the visible portion of the workspace, relative @@ -63,9 +63,6 @@ export class ViewportChange extends UiBase { /** The old scale of the workspace. */ this.oldScale = opt_oldScale; - - /** Type of this event. */ - this.type = eventUtils.VIEWPORT_CHANGE; } /** @@ -73,8 +70,28 @@ export class ViewportChange extends UiBase { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = super.toJson(); + override toJson(): ViewportChangeJson { + const json = super.toJson() as ViewportChangeJson; + if (this.viewTop === undefined) { + throw new Error( + 'The view top is undefined. Either pass a value to ' + + 'the constructor, or call fromJson'); + } + if (this.viewLeft === undefined) { + throw new Error( + 'The view left is undefined. Either pass a value to ' + + 'the constructor, or call fromJson'); + } + if (this.scale === undefined) { + throw new Error( + 'The scale is undefined. Either pass a value to ' + + 'the constructor, or call fromJson'); + } + if (this.oldScale === undefined) { + throw new Error( + 'The old scale is undefined. Either pass a value to ' + + 'the constructor, or call fromJson'); + } json['viewTop'] = this.viewTop; json['viewLeft'] = this.viewLeft; json['scale'] = this.scale; @@ -87,7 +104,7 @@ export class ViewportChange extends UiBase { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { + override fromJson(json: ViewportChangeJson) { super.fromJson(json); this.viewTop = json['viewTop']; this.viewLeft = json['viewLeft']; @@ -96,5 +113,12 @@ export class ViewportChange extends UiBase { } } +export interface ViewportChangeJson extends AbstractEventJson { + viewTop: number; + viewLeft: number; + scale: number; + oldScale: number; +} + registry.register( registry.Type.EVENT, eventUtils.VIEWPORT_CHANGE, ViewportChange); diff --git a/core/events/utils.ts b/core/events/utils.ts index 782728eee..9938216ce 100644 --- a/core/events/utils.ts +++ b/core/events/utils.ts @@ -536,6 +536,9 @@ export function disableOrphans(event: Abstract) { } const eventWorkspace = common.getWorkspaceById(blockEvent.workspaceId) as WorkspaceSvg; + if (!blockEvent.blockId) { + throw new Error('Encountered a blockEvent without a proper blockId'); + } let block = eventWorkspace.getBlockById(blockEvent.blockId); if (block) { // Changing blocks as part of this event shouldn't be undoable. diff --git a/core/events/workspace_events.ts b/core/events/workspace_events.ts index 2e140a538..95b533d54 100644 --- a/core/events/workspace_events.ts +++ b/core/events/workspace_events.ts @@ -14,8 +14,7 @@ goog.declareModuleId('Blockly.Events.FinishedLoading'); import * as registry from '../registry.js'; import type {Workspace} from '../workspace.js'; - -import {Abstract as AbstractEvent} from './events_abstract.js'; +import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js'; import * as eventUtils from './utils.js'; @@ -28,13 +27,9 @@ import * as eventUtils from './utils.js'; * @alias Blockly.Events.FinishedLoading */ export class FinishedLoading extends AbstractEvent { - override isBlank: boolean; - override workspaceId: string; - - // Workspace events do not undo or redo. + override isBlank = true; override recordUndo = false; - override type: string; - override group: AnyDuringMigration; + override type = eventUtils.FINISHED_LOADING; /** * @param opt_workspace The workspace that has finished loading. Undefined @@ -43,13 +38,12 @@ export class FinishedLoading extends AbstractEvent { constructor(opt_workspace?: Workspace) { super(); /** Whether or not the event is blank (to be populated by fromJson). */ - this.isBlank = typeof opt_workspace === 'undefined'; + this.isBlank = !!opt_workspace; + + if (!opt_workspace) return; /** The workspace identifier for this event. */ - this.workspaceId = opt_workspace ? opt_workspace.id : ''; - - /** Type of this event. */ - this.type = eventUtils.FINISHED_LOADING; + this.workspaceId = opt_workspace.id; } /** @@ -57,16 +51,14 @@ export class FinishedLoading extends AbstractEvent { * * @returns JSON representation. */ - override toJson(): AnyDuringMigration { - const json = { - 'type': this.type, - }; - if (this.group) { - (json as AnyDuringMigration)['group'] = this.group; - } - if (this.workspaceId) { - (json as AnyDuringMigration)['workspaceId'] = this.workspaceId; + override toJson(): FinishedLoadingJson { + const json = super.toJson() as FinishedLoadingJson; + if (!this.workspaceId) { + throw new Error( + 'The workspace ID is undefined. Either pass a workspace to ' + + 'the constructor, or call fromJson'); } + json['workspaceId'] = this.workspaceId; return json; } @@ -75,12 +67,15 @@ export class FinishedLoading extends AbstractEvent { * * @param json JSON representation. */ - override fromJson(json: AnyDuringMigration) { - this.isBlank = false; + override fromJson(json: FinishedLoadingJson) { + super.fromJson(json); this.workspaceId = json['workspaceId']; - this.group = json['group']; } } +export interface FinishedLoadingJson extends AbstractEventJson { + workspaceId: string; +} + registry.register( registry.Type.EVENT, eventUtils.FINISHED_LOADING, FinishedLoading); diff --git a/core/trashcan.ts b/core/trashcan.ts index 742065393..18bd2e370 100644 --- a/core/trashcan.ts +++ b/core/trashcan.ts @@ -552,6 +552,9 @@ export class Trashcan extends DeleteArea implements IAutoHideable, } const deleteEvent = event as BlockDelete; if (event.type === eventUtils.BLOCK_DELETE && !deleteEvent.wasShadow) { + if (!deleteEvent.oldJson) { + throw new Error('Encountered a delete event without proper oldJson'); + } const cleanedJson = JSON.stringify(this.cleanBlockJson_(deleteEvent.oldJson)); if (this.contents_.indexOf(cleanedJson) !== -1) { diff --git a/core/utils/coordinate.ts b/core/utils/coordinate.ts index 5d2411a14..80ef56e00 100644 --- a/core/utils/coordinate.ts +++ b/core/utils/coordinate.ts @@ -69,7 +69,7 @@ export class Coordinate { * @param b A Coordinate. * @returns True iff the coordinates are equal, or if both are null. */ - static equals(a: Coordinate|null, b: Coordinate|null): boolean { + static equals(a?: Coordinate|null, b?: Coordinate|null): boolean { if (a === b) { return true; } diff --git a/tests/mocha/event_test.js b/tests/mocha/event_test.js index ba3209cbf..9954225be 100644 --- a/tests/mocha/event_test.js +++ b/tests/mocha/event_test.js @@ -61,7 +61,7 @@ suite('Events', function() { suite('Constructors', function() { test('Abstract', function() { const event = new Blockly.Events.Abstract(); - assertEventEquals(event, undefined, undefined, undefined, { + assertEventEquals(event, '', undefined, undefined, { 'recordUndo': true, 'group': '', }); @@ -69,7 +69,7 @@ suite('Events', function() { test('UI event without block', function() { const event = new Blockly.Events.UiBase(this.workspace.id); - assertEventEquals(event, undefined, this.workspace.id, undefined, { + assertEventEquals(event, '', this.workspace.id, undefined, { 'recordUndo': false, 'group': '', }, true); @@ -110,10 +110,9 @@ suite('Events', function() { test('Block base', function() { const event = new Blockly.Events.BlockBase(this.block); sinon.assert.calledOnce(this.genUidStub); - assertEventEquals(event, undefined, + assertEventEquals(event, '', this.workspace.id, this.TEST_BLOCK_ID, { - 'varId': undefined, 'recordUndo': true, 'group': '', }); @@ -223,7 +222,7 @@ suite('Events', function() { test('Block base', function() { const event = new Blockly.Events.BlockBase(this.block); sinon.assert.calledOnce(this.genUidStub); - assertEventEquals(event, undefined, + assertEventEquals(event, '', this.workspace.id, this.TEST_BLOCK_ID, { 'varId': undefined, @@ -339,51 +338,51 @@ suite('Events', function() { const variableEventTestCases = [ {title: 'Var create', class: Blockly.Events.VarCreate, getArgs: (thisObj) => [thisObj.variable], - getExpectedJson: () => ({type: 'var_create', varId: 'id1', + getExpectedJson: () => ({type: 'var_create', group: '', varId: 'id1', varType: 'type1', varName: 'name1'})}, {title: 'Var delete', class: Blockly.Events.VarDelete, getArgs: (thisObj) => [thisObj.variable], - getExpectedJson: () => ({type: 'var_delete', varId: 'id1', + getExpectedJson: () => ({type: 'var_delete', group: '', varId: 'id1', varType: 'type1', varName: 'name1'})}, {title: 'Var rename', class: Blockly.Events.VarRename, getArgs: (thisObj) => [thisObj.variable, 'name2'], - getExpectedJson: () => ({type: 'var_rename', varId: 'id1', + getExpectedJson: () => ({type: 'var_rename', group: '', varId: 'id1', oldName: 'name1', newName: 'name2'})}, ]; const uiEventTestCases = [ {title: 'Bubble open', class: Blockly.Events.BubbleOpen, getArgs: (thisObj) => [thisObj.block, true, 'mutator'], - getExpectedJson: (thisObj) => ({type: 'bubble_open', isOpen: true, + getExpectedJson: (thisObj) => ({type: 'bubble_open', group: '', isOpen: true, bubbleType: 'mutator', blockId: thisObj.block.id})}, {title: 'Block click', class: Blockly.Events.Click, getArgs: (thisObj) => [thisObj.block, null, 'block'], - getExpectedJson: (thisObj) => ({type: 'click', targetType: 'block', + getExpectedJson: (thisObj) => ({type: 'click', group: '', targetType: 'block', blockId: thisObj.block.id})}, {title: 'Workspace click', class: Blockly.Events.Click, getArgs: (thisObj) => [null, thisObj.workspace.id, 'workspace'], - getExpectedJson: (thisObj) => ({type: 'click', + getExpectedJson: (thisObj) => ({type: 'click', group: '', targetType: 'workspace'})}, {title: 'Drag start', class: Blockly.Events.BlockDrag, getArgs: (thisObj) => [thisObj.block, true, [thisObj.block]], - getExpectedJson: (thisObj) => ({type: 'drag', + getExpectedJson: (thisObj) => ({type: 'drag', group: '', isStart: true, blockId: thisObj.block.id, blocks: [thisObj.block]})}, {title: 'Drag end', class: Blockly.Events.BlockDrag, getArgs: (thisObj) => [thisObj.block, false, [thisObj.block]], - getExpectedJson: (thisObj) => ({type: 'drag', + getExpectedJson: (thisObj) => ({type: 'drag', group: '', isStart: false, blockId: thisObj.block.id, blocks: [thisObj.block]})}, {title: 'null to Block Marker move', class: Blockly.Events.MarkerMove, getArgs: (thisObj) => [thisObj.block, true, null, new ASTNode(ASTNode.types.BLOCK, thisObj.block)], - getExpectedJson: (thisObj) => ({type: 'marker_move', - isCursor: true, blockId: thisObj.block.id, oldNode: null, + getExpectedJson: (thisObj) => ({type: 'marker_move', group: '', + isCursor: true, blockId: thisObj.block.id, oldNode: undefined, newNode: new ASTNode(ASTNode.types.BLOCK, thisObj.block)})}, {title: 'null to Workspace Marker move', class: Blockly.Events.MarkerMove, getArgs: (thisObj) => [null, true, null, ASTNode.createWorkspaceNode(thisObj.workspace, new Blockly.utils.Coordinate(0, 0))], - getExpectedJson: (thisObj) => ({type: 'marker_move', - isCursor: true, blockId: null, oldNode: null, + getExpectedJson: (thisObj) => ({type: 'marker_move', group: '', + isCursor: true, blockId: undefined, oldNode: undefined, newNode: ASTNode.createWorkspaceNode(thisObj.workspace, new Blockly.utils.Coordinate(0, 0))})}, {title: 'Workspace to Block Marker move', @@ -392,7 +391,7 @@ suite('Events', function() { ASTNode.createWorkspaceNode(thisObj.workspace, new Blockly.utils.Coordinate(0, 0)), new ASTNode(ASTNode.types.BLOCK, thisObj.block)], - getExpectedJson: (thisObj) => ({type: 'marker_move', + getExpectedJson: (thisObj) => ({type: 'marker_move', group: '', isCursor: true, blockId: thisObj.block.id, oldNode: ASTNode.createWorkspaceNode(thisObj.workspace, new Blockly.utils.Coordinate(0, 0)), @@ -406,40 +405,39 @@ suite('Events', function() { new Blockly.utils.Coordinate(0, 0))]}, {title: 'Selected', class: Blockly.Events.Selected, getArgs: (thisObj) => [null, thisObj.block.id, thisObj.workspace.id], - getExpectedJson: (thisObj) => ({type: 'selected', oldElementId: null, + getExpectedJson: (thisObj) => ({type: 'selected', group: '', newElementId: thisObj.block.id})}, {title: 'Selected (deselect)', class: Blockly.Events.Selected, getArgs: (thisObj) => [thisObj.block.id, null, thisObj.workspace.id], - getExpectedJson: (thisObj) => ({type: 'selected', - oldElementId: thisObj.block.id, newElementId: null})}, + getExpectedJson: (thisObj) => ({type: 'selected', group: '', + oldElementId: thisObj.block.id})}, {title: 'Theme Change', class: Blockly.Events.ThemeChange, getArgs: (thisObj) => ['classic', thisObj.workspace.id], - getExpectedJson: () => ({type: 'theme_change', themeName: 'classic'})}, + getExpectedJson: () => ({type: 'theme_change', group: '', themeName: 'classic'})}, {title: 'Toolbox item select', class: Blockly.Events.ToolboxItemSelect, getArgs: (thisObj) => ['Math', 'Loops', thisObj.workspace.id], - getExpectedJson: () => ({type: 'toolbox_item_select', oldItem: 'Math', + getExpectedJson: () => ({type: 'toolbox_item_select', group: '', oldItem: 'Math', newItem: 'Loops'})}, {title: 'Toolbox item select (no previous)', class: Blockly.Events.ToolboxItemSelect, getArgs: (thisObj) => [null, 'Loops', thisObj.workspace.id], - getExpectedJson: () => ({type: 'toolbox_item_select', oldItem: null, + getExpectedJson: () => ({type: 'toolbox_item_select', group: '', newItem: 'Loops'})}, {title: 'Toolbox item select (deselect)', class: Blockly.Events.ToolboxItemSelect, getArgs: (thisObj) => ['Math', null, thisObj.workspace.id], - getExpectedJson: () => ({type: 'toolbox_item_select', oldItem: 'Math', - newItem: null})}, + getExpectedJson: () => ({type: 'toolbox_item_select', group: '', oldItem: 'Math'})}, {title: 'Trashcan open', class: Blockly.Events.TrashcanOpen, getArgs: (thisObj) => [true, thisObj.workspace.id], - getExpectedJson: () => ({type: 'trashcan_open', isOpen: true})}, + getExpectedJson: () => ({type: 'trashcan_open', group: '', isOpen: true})}, {title: 'Viewport change', class: Blockly.Events.ViewportChange, getArgs: (thisObj) => [2.666, 1.333, 1.2, thisObj.workspace.id, 1], - getExpectedJson: () => ({type: 'viewport_change', viewTop: 2.666, + getExpectedJson: () => ({type: 'viewport_change', group: '', viewTop: 2.666, viewLeft: 1.333, scale: 1.2, oldScale: 1})}, {title: 'Viewport change (0,0)', class: Blockly.Events.ViewportChange, getArgs: (thisObj) => [0, 0, 1.2, thisObj.workspace.id, 1], - getExpectedJson: () => ({type: 'viewport_change', viewTop: 0, + getExpectedJson: () => ({type: 'viewport_change', group: '', viewTop: 0, viewLeft: 0, scale: 1.2, oldScale: 1})}, ]; const blockEventTestCases = [ @@ -449,6 +447,7 @@ suite('Events', function() { getArgs: (thisObj) => [thisObj.block, 'collapsed', null, false, true], getExpectedJson: (thisObj) => ({ type: 'change', + group: '', blockId: thisObj.block.id, element: 'collapsed', oldValue: false, @@ -461,6 +460,7 @@ suite('Events', function() { getArgs: (thisObj) => [thisObj.block], getExpectedJson: (thisObj) => ({ type: 'create', + group: '', blockId: thisObj.block.id, xml: '' + @@ -480,6 +480,7 @@ suite('Events', function() { getArgs: (thisObj) => [thisObj.shadowBlock], getExpectedJson: (thisObj) => ({ type: 'create', + group: '', blockId: thisObj.shadowBlock.id, xml: '' + @@ -500,6 +501,7 @@ suite('Events', function() { getArgs: (thisObj) => [thisObj.block], getExpectedJson: (thisObj) => ({ type: 'delete', + group: '', blockId: thisObj.block.id, oldXml: '' + @@ -520,6 +522,7 @@ suite('Events', function() { getArgs: (thisObj) => [thisObj.shadowBlock], getExpectedJson: (thisObj) => ({ type: 'delete', + group: '', blockId: thisObj.shadowBlock.id, oldXml: '' + @@ -542,6 +545,7 @@ suite('Events', function() { getArgs: (thisObj) => [thisObj.block], getExpectedJson: (thisObj) => ({ type: 'move', + group: '', blockId: thisObj.block.id, }), }, @@ -551,6 +555,7 @@ suite('Events', function() { getArgs: (thisObj) => [thisObj.shadowBlock], getExpectedJson: (thisObj) => ({ type: 'move', + group: '', blockId: thisObj.shadowBlock.id, recordUndo: false, }), @@ -559,29 +564,25 @@ suite('Events', function() { const workspaceEventTestCases = [ {title: 'Finished Loading', class: Blockly.Events.FinishedLoading, getArgs: (thisObj) => [thisObj.workspace], - getExpectedJson: (thisObj) => ({type: 'finished_loading', + getExpectedJson: (thisObj) => ({type: 'finished_loading', group: '', workspaceId: thisObj.workspace.id})}, ]; const workspaceCommentEventTestCases = [ {title: 'Comment change', class: Blockly.Events.CommentChange, getArgs: (thisObj) => [thisObj.comment, 'bar', 'foo'], - getExpectedJson: (thisObj) => ({type: 'comment_change', + getExpectedJson: (thisObj) => ({type: 'comment_change', group: '', commentId: thisObj.comment.id, oldContents: 'bar', newContents: 'foo'})}, {title: 'Comment create', class: Blockly.Events.CommentCreate, getArgs: (thisObj) => [thisObj.comment], - getExpectedJson: (thisObj) => ({type: 'comment_create', + getExpectedJson: (thisObj) => ({type: 'comment_create', group: '', commentId: thisObj.comment.id, xml: Blockly.Xml.domToText(thisObj.comment.toXmlWithXY())})}, {title: 'Comment delete', class: Blockly.Events.CommentDelete, getArgs: (thisObj) => [thisObj.comment], - getExpectedJson: (thisObj) => ({type: 'comment_delete', + getExpectedJson: (thisObj) => ({type: 'comment_delete', group: '', commentId: thisObj.comment.id})}, // TODO(#4577) Test serialization of move event coordinate properties. - {title: 'Comment move', class: Blockly.Events.CommentMove, - getArgs: (thisObj) => [thisObj.comment], - getExpectedJson: (thisObj) => ({type: 'comment_move', - commentId: thisObj.comment.id, oldCoordinate: '0,0'})}, ]; const testSuites = [ {title: 'Variable events', testCases: variableEventTestCases, @@ -670,7 +671,7 @@ suite('Events', function() { suite('Constructors', function() { test('Var base', function() { const event = new Blockly.Events.VarBase(this.variable); - assertEventEquals(event, undefined, this.workspace.id, undefined, { + assertEventEquals(event, '', this.workspace.id, undefined, { 'varId': 'id1', 'recordUndo': true, 'group': '', diff --git a/tests/mocha/gesture_test.js b/tests/mocha/gesture_test.js index 3efbb1acc..1ee0c75ca 100644 --- a/tests/mocha/gesture_test.js +++ b/tests/mocha/gesture_test.js @@ -38,7 +38,7 @@ suite('Gesture', function() { assertEventFired(eventsFireStub, Blockly.Events.Selected, - {oldElementId: null, newElementId: block.id, type: eventUtils.SELECTED}, fieldWorkspace.id); + {newElementId: block.id, type: eventUtils.SELECTED}, fieldWorkspace.id); assertEventNotFired(eventsFireStub, Blockly.Events.Click, {type: eventUtils.CLICK}); } diff --git a/tests/mocha/trashcan_test.js b/tests/mocha/trashcan_test.js index f52a9b55d..5c2f3c3be 100644 --- a/tests/mocha/trashcan_test.js +++ b/tests/mocha/trashcan_test.js @@ -83,7 +83,7 @@ suite("Trashcan", function() { this.eventsFireStub, Blockly.Events.TrashcanOpen, {type: eventUtils.CLICK}); assertEventFired( this.eventsFireStub, Blockly.Events.Click, {targetType: 'workspace', type: eventUtils.CLICK}, - this.workspace.id, null); + this.workspace.id, undefined); }); test("Click with contents - fires trashcanOpen", function() { fireDeleteEvent(this.workspace, ''); @@ -115,7 +115,7 @@ suite("Trashcan", function() { {isOpen: false, type: eventUtils.TRASHCAN_OPEN}, this.workspace.id); assertEventFired( this.eventsFireStub, Blockly.Events.Click, {targetType: 'workspace', type: eventUtils.CLICK}, - this.workspace.id, null); + this.workspace.id, undefined); }); }); suite("Unique Contents", function() { diff --git a/tests/mocha/zoom_controls_test.js b/tests/mocha/zoom_controls_test.js index 5bbda917e..5154fd704 100644 --- a/tests/mocha/zoom_controls_test.js +++ b/tests/mocha/zoom_controls_test.js @@ -34,7 +34,7 @@ suite("Zoom Controls", function() { assertEventFired( this.eventsFireStub, Blockly.Events.Click, - {targetType: 'zoom_controls', type: eventUtils.CLICK}, this.workspace.id, null); + {targetType: 'zoom_controls', type: eventUtils.CLICK}, this.workspace.id, undefined); assertEventNotFired( this.eventsFireStub, Blockly.Events.Click, {targetType: 'workspace', type: eventUtils.CLICK}); @@ -45,7 +45,7 @@ suite("Zoom Controls", function() { assertEventFired( this.eventsFireStub, Blockly.Events.Click, - {targetType: 'zoom_controls', type: eventUtils.CLICK}, this.workspace.id, null); + {targetType: 'zoom_controls', type: eventUtils.CLICK}, this.workspace.id, undefined); assertEventNotFired( this.eventsFireStub, Blockly.Events.Click, {targetType: 'workspace', type: eventUtils.CLICK}); @@ -56,7 +56,7 @@ suite("Zoom Controls", function() { assertEventFired( this.eventsFireStub, Blockly.Events.Click, - {targetType: 'zoom_controls', type: eventUtils.CLICK}, this.workspace.id, null); + {targetType: 'zoom_controls', type: eventUtils.CLICK}, this.workspace.id, undefined); assertEventNotFired( this.eventsFireStub, Blockly.Events.Click, {targetType: 'workspace', type: eventUtils.CLICK});