From aff572d8d300cb8bec7507ca46498eb2248e5154 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 8 Jan 2025 10:26:08 -0800 Subject: [PATCH] chore: Revert removal of XML-based category functions. --- core/procedures.ts | 121 ++++++++++++++++++++++++++++++++- core/variables.ts | 137 ++++++++++++++++++++++++++++++++++++-- core/variables_dynamic.ts | 112 +++++++++++++++++++++++++++++-- core/workspace_svg.ts | 12 ++-- 4 files changed, 365 insertions(+), 17 deletions(-) diff --git a/core/procedures.ts b/core/procedures.ts index 48342d057e..db6b68b712 100644 --- a/core/procedures.ts +++ b/core/procedures.ts @@ -237,9 +237,126 @@ export function rename(this: Field, name: string): string { * Construct the blocks required by the flyout for the procedure category. * * @param workspace The workspace containing procedures. - * @returns Array of JSON block elements. + * @returns Array of XML block elements. */ -export function flyoutCategory(workspace: WorkspaceSvg): FlyoutItemInfo[] { +function xmlFlyoutCategory(workspace: WorkspaceSvg): Element[] { + const xmlList = []; + if (Blocks['procedures_defnoreturn']) { + // + // do something + // + const block = utilsXml.createElement('block'); + block.setAttribute('type', 'procedures_defnoreturn'); + block.setAttribute('gap', '16'); + const nameField = utilsXml.createElement('field'); + nameField.setAttribute('name', 'NAME'); + nameField.appendChild( + utilsXml.createTextNode(Msg['PROCEDURES_DEFNORETURN_PROCEDURE']), + ); + block.appendChild(nameField); + xmlList.push(block); + } + if (Blocks['procedures_defreturn']) { + // + // do something + // + const block = utilsXml.createElement('block'); + block.setAttribute('type', 'procedures_defreturn'); + block.setAttribute('gap', '16'); + const nameField = utilsXml.createElement('field'); + nameField.setAttribute('name', 'NAME'); + nameField.appendChild( + utilsXml.createTextNode(Msg['PROCEDURES_DEFRETURN_PROCEDURE']), + ); + block.appendChild(nameField); + xmlList.push(block); + } + if (Blocks['procedures_ifreturn']) { + // + const block = utilsXml.createElement('block'); + block.setAttribute('type', 'procedures_ifreturn'); + block.setAttribute('gap', '16'); + xmlList.push(block); + } + if (xmlList.length) { + // Add slightly larger gap between system blocks and user calls. + xmlList[xmlList.length - 1].setAttribute('gap', '24'); + } + + /** + * Add items to xmlList for each listed procedure. + * + * @param procedureList A list of procedures, each of which is defined by a + * three-element list of name, parameter list, and return value boolean. + * @param templateName The type of the block to generate. + */ + function populateProcedures( + procedureList: ProcedureTuple[], + templateName: string, + ) { + for (let i = 0; i < procedureList.length; i++) { + const name = procedureList[i][0]; + const args = procedureList[i][1]; + // + // + // + // + // + const block = utilsXml.createElement('block'); + block.setAttribute('type', templateName); + block.setAttribute('gap', '16'); + const mutation = utilsXml.createElement('mutation'); + mutation.setAttribute('name', name); + block.appendChild(mutation); + for (let j = 0; j < args.length; j++) { + const arg = utilsXml.createElement('arg'); + arg.setAttribute('name', args[j]); + mutation.appendChild(arg); + } + xmlList.push(block); + } + } + + const tuple = allProcedures(workspace); + populateProcedures(tuple[0], 'procedures_callnoreturn'); + populateProcedures(tuple[1], 'procedures_callreturn'); + return xmlList; +} + +/** + * Internal wrapper that returns the contents of the procedure category. + * + * @internal + * @param workspace The workspace to populate procedure blocks for. + */ +export function internalFlyoutCategory( + workspace: WorkspaceSvg, +): FlyoutItemInfo[] { + return flyoutCategory(workspace, false); +} + +export function flyoutCategory( + workspace: WorkspaceSvg, + useXml: true, +): Element[]; +export function flyoutCategory( + workspace: WorkspaceSvg, + useXml: false, +): FlyoutItemInfo[]; +/** + * Construct the blocks required by the flyout for the procedure category. + * + * @param workspace The workspace containing procedures. + * @param useXml True to return the contents as XML, false to use JSON. + * @returns List of flyout contents as either XML or JSON. + */ +export function flyoutCategory( + workspace: WorkspaceSvg, + useXml = true, +): Element[] | FlyoutItemInfo[] { + if (useXml) { + return xmlFlyoutCategory(workspace); + } const blocks = []; if (Blocks['procedures_defnoreturn']) { blocks.push({ diff --git a/core/variables.ts b/core/variables.ts index 0c04251d01..5407a0906a 100644 --- a/core/variables.ts +++ b/core/variables.ts @@ -14,6 +14,7 @@ import {isVariableBackedParameterModel} from './interfaces/i_variable_backed_par import {IVariableModel, IVariableState} from './interfaces/i_variable_model.js'; import {Msg} from './msg.js'; import type {BlockInfo, FlyoutItemInfo} from './utils/toolbox.js'; +import * as utilsXml from './utils/xml.js'; import type {Workspace} from './workspace.js'; import type {WorkspaceSvg} from './workspace_svg.js'; @@ -84,20 +85,48 @@ export function allDeveloperVariables(workspace: Workspace): string[] { return Array.from(variables.values()); } +/** + * Internal wrapper that returns the contents of the variables category. + * + * @internal + * @param workspace The workspace to populate variable blocks for. + */ +export function internalFlyoutCategory( + workspace: WorkspaceSvg, +): FlyoutItemInfo[] { + return flyoutCategory(workspace, false); +} + +export function flyoutCategory( + workspace: WorkspaceSvg, + useXml: true, +): Element[]; +export function flyoutCategory( + workspace: WorkspaceSvg, + useXml: false, +): FlyoutItemInfo[]; /** * Construct the elements (blocks and button) required by the flyout for the * variable category. * * @param workspace The workspace containing variables. - * @returns JSON list of flyout contents. + * @param useXml True to return the contents as XML, false to use JSON. + * @returns List of flyout contents as either XML or JSON. */ -export function flyoutCategory(workspace: WorkspaceSvg): FlyoutItemInfo[] { +export function flyoutCategory( + workspace: WorkspaceSvg, + useXml = true, +): Element[] | FlyoutItemInfo[] { if (!Blocks['variables_set'] && !Blocks['variables_get']) { console.warn( 'There are no variable blocks, but there is a variable category.', ); } + if (useXml) { + return xmlFlyoutCategory(workspace); + } + workspace.registerButtonCallback('CREATE_VARIABLE', function (button) { createVariableButtonHandler(button.getTargetWorkspace()); }); @@ -108,7 +137,11 @@ export function flyoutCategory(workspace: WorkspaceSvg): FlyoutItemInfo[] { 'text': '%{BKY_NEW_VARIABLE}', 'callbackkey': 'CREATE_VARIABLE', }, - ...flyoutCategoryBlocks(workspace, workspace.getVariablesOfType(''), true), + ...jsonFlyoutCategoryBlocks( + workspace, + workspace.getVariablesOfType(''), + true, + ), ]; } @@ -130,6 +163,7 @@ function generateVariableFieldJson(variable: IVariableModel) { /** * Construct the blocks required by the flyout for the variable category. * + * @internal * @param workspace The workspace containing variables. * @param variables List of variables to create blocks for. * @param includeChangeBlocks True to include `change x by _` blocks. @@ -137,7 +171,7 @@ function generateVariableFieldJson(variable: IVariableModel) { * @param setterType The type of the variable setter block to generate. * @returns JSON list of blocks. */ -export function flyoutCategoryBlocks( +export function jsonFlyoutCategoryBlocks( workspace: Workspace, variables: IVariableModel[], includeChangeBlocks: boolean, @@ -196,6 +230,80 @@ export function flyoutCategoryBlocks( return blocks; } +/** + * Construct the elements (blocks and button) required by the flyout for the + * variable category. + * + * @param workspace The workspace containing variables. + * @returns Array of XML elements. + */ +function xmlFlyoutCategory(workspace: WorkspaceSvg): Element[] { + let xmlList = new Array(); + const button = document.createElement('button'); + button.setAttribute('text', '%{BKY_NEW_VARIABLE}'); + button.setAttribute('callbackKey', 'CREATE_VARIABLE'); + + workspace.registerButtonCallback('CREATE_VARIABLE', function (button) { + createVariableButtonHandler(button.getTargetWorkspace()); + }); + + xmlList.push(button); + + const blockList = flyoutCategoryBlocks(workspace); + xmlList = xmlList.concat(blockList); + return xmlList; +} + +/** + * Construct the blocks required by the flyout for the variable category. + * + * @param workspace The workspace containing variables. + * @returns Array of XML block elements. + */ +export function flyoutCategoryBlocks(workspace: Workspace): Element[] { + const variableModelList = workspace.getVariablesOfType(''); + + const xmlList = []; + if (variableModelList.length > 0) { + // New variables are added to the end of the variableModelList. + const mostRecentVariable = variableModelList[variableModelList.length - 1]; + if (Blocks['variables_set']) { + const block = utilsXml.createElement('block'); + block.setAttribute('type', 'variables_set'); + block.setAttribute('gap', Blocks['math_change'] ? '8' : '24'); + block.appendChild(generateVariableFieldDom(mostRecentVariable)); + xmlList.push(block); + } + if (Blocks['math_change']) { + const block = utilsXml.createElement('block'); + block.setAttribute('type', 'math_change'); + block.setAttribute('gap', Blocks['variables_get'] ? '20' : '8'); + block.appendChild(generateVariableFieldDom(mostRecentVariable)); + const value = utilsXml.textToDom( + '' + + '' + + '1' + + '' + + '', + ); + block.appendChild(value); + xmlList.push(block); + } + + if (Blocks['variables_get']) { + variableModelList.sort(compareByName); + for (let i = 0, variable; (variable = variableModelList[i]); i++) { + const block = utilsXml.createElement('block'); + block.setAttribute('type', 'variables_get'); + block.setAttribute('gap', '8'); + block.appendChild(generateVariableFieldDom(variable)); + xmlList.push(block); + } + } + } + return xmlList; +} + export const VAR_LETTER_OPTIONS = 'ijkmnopqrstuvwxyzabcdefgh'; /** @@ -535,6 +643,27 @@ function checkForConflictingParamWithLegacyProcedures( return null; } +/** + * Generate DOM objects representing a variable field. + * + * @param variableModel The variable model to represent. + * @returns The generated DOM. + */ +export function generateVariableFieldDom( + variableModel: IVariableModel, +): Element { + /* Generates the following XML: + * foo + */ + const field = utilsXml.createElement('field'); + field.setAttribute('name', 'VAR'); + field.setAttribute('id', variableModel.getId()); + field.setAttribute('variabletype', variableModel.getType()); + const name = utilsXml.createTextNode(variableModel.getName()); + field.appendChild(name); + return field; +} + /** * Helper function to look up or create a variable on the given workspace. * If no variable exists, creates and returns it. diff --git a/core/variables_dynamic.ts b/core/variables_dynamic.ts index 50eef2ec1d..994ab83a11 100644 --- a/core/variables_dynamic.ts +++ b/core/variables_dynamic.ts @@ -10,7 +10,9 @@ import {Blocks} from './blocks.js'; import type {FlyoutButton} from './flyout_button.js'; import {Msg} from './msg.js'; import type {FlyoutItemInfo} from './utils/toolbox.js'; +import * as xml from './utils/xml.js'; import * as Variables from './variables.js'; +import type {Workspace} from './workspace.js'; import type {WorkspaceSvg} from './workspace_svg.js'; /** @@ -67,20 +69,47 @@ function colourButtonClickHandler(button: FlyoutButton) { // eslint-disable-next-line camelcase export const onCreateVariableButtonClick_Colour = colourButtonClickHandler; +/** + * Internal wrapper that returns the contents of the dynamic variables category. + * + * @internal + * @param workspace The workspace to populate variable blocks for. + */ +export function internalFlyoutCategory( + workspace: WorkspaceSvg, +): FlyoutItemInfo[] { + return flyoutCategory(workspace, false); +} + +export function flyoutCategory( + workspace: WorkspaceSvg, + useXml: true, +): Element[]; +export function flyoutCategory( + workspace: WorkspaceSvg, + useXml: false, +): FlyoutItemInfo[]; /** * Construct the elements (blocks and button) required by the flyout for the - * variable category. + * dynamic variables category. * - * @param workspace The workspace containing variables. - * @returns JSON list of flyout contents. + * @param useXml True to return the contents as XML, false to use JSON. + * @returns List of flyout contents as either XML or JSON. */ -export function flyoutCategory(workspace: WorkspaceSvg): FlyoutItemInfo[] { +export function flyoutCategory( + workspace: WorkspaceSvg, + useXml = true, +): Element[] | FlyoutItemInfo[] { if (!Blocks['variables_set_dynamic'] && !Blocks['variables_get_dynamic']) { console.warn( 'There are no dynamic variable blocks, but there is a dynamic variable category.', ); } + if (useXml) { + return xmlFlyoutCategory(workspace); + } + workspace.registerButtonCallback( 'CREATE_VARIABLE_STRING', stringButtonClickHandler, @@ -110,7 +139,7 @@ export function flyoutCategory(workspace: WorkspaceSvg): FlyoutItemInfo[] { 'text': Msg['NEW_COLOUR_VARIABLE'], 'callbackkey': 'CREATE_VARIABLE_COLOUR', }, - ...Variables.flyoutCategoryBlocks( + ...Variables.jsonFlyoutCategoryBlocks( workspace, workspace.getAllVariables(), false, @@ -119,3 +148,76 @@ export function flyoutCategory(workspace: WorkspaceSvg): FlyoutItemInfo[] { ), ]; } + +/** + * Construct the elements (blocks and button) required by the flyout for the + * variable category. + * + * @param workspace The workspace containing variables. + * @returns Array of XML elements. + */ +function xmlFlyoutCategory(workspace: WorkspaceSvg): Element[] { + let xmlList = new Array(); + let button = document.createElement('button'); + button.setAttribute('text', Msg['NEW_STRING_VARIABLE']); + button.setAttribute('callbackKey', 'CREATE_VARIABLE_STRING'); + xmlList.push(button); + button = document.createElement('button'); + button.setAttribute('text', Msg['NEW_NUMBER_VARIABLE']); + button.setAttribute('callbackKey', 'CREATE_VARIABLE_NUMBER'); + xmlList.push(button); + button = document.createElement('button'); + button.setAttribute('text', Msg['NEW_COLOUR_VARIABLE']); + button.setAttribute('callbackKey', 'CREATE_VARIABLE_COLOUR'); + xmlList.push(button); + + workspace.registerButtonCallback( + 'CREATE_VARIABLE_STRING', + stringButtonClickHandler, + ); + workspace.registerButtonCallback( + 'CREATE_VARIABLE_NUMBER', + numberButtonClickHandler, + ); + workspace.registerButtonCallback( + 'CREATE_VARIABLE_COLOUR', + colourButtonClickHandler, + ); + + const blockList = flyoutCategoryBlocks(workspace); + xmlList = xmlList.concat(blockList); + return xmlList; +} + +/** + * Construct the blocks required by the flyout for the variable category. + * + * @param workspace The workspace containing variables. + * @returns Array of XML block elements. + */ +export function flyoutCategoryBlocks(workspace: Workspace): Element[] { + const variableModelList = workspace.getAllVariables(); + + const xmlList = []; + if (variableModelList.length > 0) { + if (Blocks['variables_set_dynamic']) { + const firstVariable = variableModelList[variableModelList.length - 1]; + const block = xml.createElement('block'); + block.setAttribute('type', 'variables_set_dynamic'); + block.setAttribute('gap', '24'); + block.appendChild(Variables.generateVariableFieldDom(firstVariable)); + xmlList.push(block); + } + if (Blocks['variables_get_dynamic']) { + variableModelList.sort(Variables.compareByName); + for (let i = 0, variable; (variable = variableModelList[i]); i++) { + const block = xml.createElement('block'); + block.setAttribute('type', 'variables_get_dynamic'); + block.setAttribute('gap', '8'); + block.appendChild(Variables.generateVariableFieldDom(variable)); + xmlList.push(block); + } + } + } + return xmlList; +} diff --git a/core/workspace_svg.ts b/core/workspace_svg.ts index 60288573ce..e9aac5b9de 100644 --- a/core/workspace_svg.ts +++ b/core/workspace_svg.ts @@ -365,24 +365,24 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { /** Manager in charge of markers and cursors. */ this.markerManager = new MarkerManager(this); - if (Variables && Variables.flyoutCategory) { + if (Variables && Variables.internalFlyoutCategory) { this.registerToolboxCategoryCallback( Variables.CATEGORY_NAME, - Variables.flyoutCategory, + Variables.internalFlyoutCategory, ); } - if (VariablesDynamic && VariablesDynamic.flyoutCategory) { + if (VariablesDynamic && VariablesDynamic.internalFlyoutCategory) { this.registerToolboxCategoryCallback( VariablesDynamic.CATEGORY_NAME, - VariablesDynamic.flyoutCategory, + VariablesDynamic.internalFlyoutCategory, ); } - if (Procedures && Procedures.flyoutCategory) { + if (Procedures && Procedures.internalFlyoutCategory) { this.registerToolboxCategoryCallback( Procedures.CATEGORY_NAME, - Procedures.flyoutCategory, + Procedures.internalFlyoutCategory, ); this.addChangeListener(Procedures.mutatorOpenListener); }