diff --git a/blocks/loops.ts b/blocks/loops.ts index 6d450e53215..3eac94fa71d 100644 --- a/blocks/loops.ts +++ b/blocks/loops.ts @@ -124,7 +124,6 @@ export const blocks = createBlockDefinitionsFromJsonArray([ { 'type': 'field_variable', 'name': 'VAR', - 'variable': null, }, { 'type': 'input_value', @@ -167,7 +166,6 @@ export const blocks = createBlockDefinitionsFromJsonArray([ { 'type': 'field_variable', 'name': 'VAR', - 'variable': null, }, { 'type': 'input_value', diff --git a/blocks/variables_dynamic.ts b/blocks/variables_dynamic.ts index 8afd24cf2e3..f50f029fcb6 100644 --- a/blocks/variables_dynamic.ts +++ b/blocks/variables_dynamic.ts @@ -38,7 +38,6 @@ export const blocks = createBlockDefinitionsFromJsonArray([ 'variable': '%{BKY_VARIABLES_DEFAULT_NAME}', }, ], - 'output': null, 'style': 'variable_dynamic_blocks', 'helpUrl': '%{BKY_VARIABLES_GET_HELPURL}', 'tooltip': '%{BKY_VARIABLES_GET_TOOLTIP}', @@ -59,6 +58,7 @@ export const blocks = createBlockDefinitionsFromJsonArray([ 'name': 'VALUE', }, ], + 'output': null, 'previousStatement': null, 'nextStatement': null, 'style': 'variable_dynamic_blocks', diff --git a/core/block.ts b/core/block.ts index af44facda5d..18e574ae13e 100644 --- a/core/block.ts +++ b/core/block.ts @@ -1721,8 +1721,8 @@ export class Block { // Validate that each arg has a corresponding message let n = 0; - while (json['args' + n]) { - if (json['message' + n] === undefined) { + while (json[`args${n}`]) { + if (json[`message${n}`] === undefined) { throw Error( warningPrefix + `args${n} must have a corresponding message (message${n}).`, @@ -1732,14 +1732,13 @@ export class Block { } // Set basic properties of block. - // Makes styles backward compatible with old way of defining hat style. - if (json['style'] && json['style'].hat) { - this.hat = json['style'].hat; + // Handle legacy style object format for backwards compatibility + if (json['style'] && typeof json['style'] === 'object') { + this.hat = (json['style'] as {hat?: string}).hat; // Must set to null so it doesn't error when checking for style and // colour. json['style'] = null; } - if (json['style'] && json['colour']) { throw Error(warningPrefix + 'Must not have both a colour and a style.'); } else if (json['style']) { @@ -1750,12 +1749,12 @@ export class Block { // Interpolate the message blocks. let i = 0; - while (json['message' + i] !== undefined) { + while (json[`message${i}`] !== undefined) { this.interpolate( - json['message' + i], - json['args' + i] || [], + json[`message${i}`] || '', + json[`args${i}`] || [], // Backwards compatibility: lastDummyAlign aliases implicitAlign. - json['implicitAlign' + i] || json['lastDummyAlign' + i], + json[`implicitAlign${i}`] || (json as any)[`lastDummyAlign${i}`], warningPrefix, ); i++; diff --git a/core/interfaces/i_json_block_definition.ts b/core/interfaces/i_json_block_definition.ts new file mode 100644 index 00000000000..90abedfe802 --- /dev/null +++ b/core/interfaces/i_json_block_definition.ts @@ -0,0 +1,119 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {FieldCheckboxFromJsonConfig} from '../field_checkbox.js'; +import {FieldDropdownFromJsonConfig} from '../field_dropdown'; +import {FieldImageFromJsonConfig} from '../field_image'; +import {FieldNumberFromJsonConfig} from '../field_number'; +import {FieldTextInputFromJsonConfig} from '../field_textinput'; +import {FieldVariableFromJsonConfig} from '../field_variable'; + +/** + * Defines the JSON structure for a block definition in Blockly. + * + * @example + * ```typescript + * const blockDef: JsonBlockDefinition = { + * type: 'custom_block', + * message0: 'move %1 steps', + * args0: [ + * { + * 'type': 'field_number', + * 'name': 'INPUT', + * }, + * ], + * previousStatement: null, + * nextStatement: null, + * }; + * ``` + */ +export interface JsonBlockDefinition { + type: string; + style?: string | null; + colour?: string | number; + output?: string | string[] | null; + previousStatement?: string | string[] | null; + nextStatement?: string | string[] | null; + outputShape?: number; + inputsInline?: boolean; + tooltip?: string; + helpUrl?: string; + extensions?: string[]; + mutator?: string; + enableContextMenu?: boolean; + suppressPrefixSuffix?: boolean; + + [key: `message${number}`]: string | undefined; + [key: `args${number}`]: BlockArg[] | undefined; + [key: `implicitAlign${number}`]: string | undefined; +} + +/** Block Arg */ +export type BlockArg = + | InputValueArg + | InputStatementArg + | InputDummyArg + | InputEndRowArg + | FieldInputArg + | FieldNumberArg + | FieldDropdownArg + | FieldCheckboxArg + | FieldImageArg + | FieldVariableArg; + +/** Input Args */ +interface InputValueArg { + type: 'input_value'; + name?: string; + check?: string | string[]; + align?: FieldsAlign; +} +interface InputStatementArg { + type: 'input_statement'; + name?: string; + check?: string | string[]; +} +interface InputDummyArg { + type: 'input_dummy'; + name?: string; +} +interface InputEndRowArg { + type: 'input_end_row'; + name?: string; +} + +/** Field Args */ +interface FieldInputArg extends FieldTextInputFromJsonConfig { + type: 'field_input'; + name?: string; +} + +interface FieldNumberArg extends FieldNumberFromJsonConfig { + type: 'field_number'; + name?: string; +} + +interface FieldDropdownArg extends FieldDropdownFromJsonConfig { + type: 'field_dropdown'; + name?: string; +} + +interface FieldCheckboxArg extends FieldCheckboxFromJsonConfig { + type: 'field_checkbox'; + name?: string; +} + +interface FieldImageArg extends FieldImageFromJsonConfig { + type: 'field_image'; + name?: string; +} + +interface FieldVariableArg extends FieldVariableFromJsonConfig { + type: 'field_variable'; + name?: string; +} + +export type FieldsAlign = 'LEFT' | 'RIGHT' | 'CENTRE';