From 8259e83992e2e59ea4f2b2eaa12b303f1bd1ea8f Mon Sep 17 00:00:00 2001 From: "Github Action (authored by pmalacho-mit)" Date: Tue, 25 Jun 2024 17:13:48 -0700 Subject: [PATCH 01/24] Adding in versioning mixin --- extensions/src/common/extension/index.ts | 3 ++- .../src/common/extension/mixins/base/index.ts | 8 ++++++-- .../extension/mixins/base/scratchInfo/index.ts | 6 +++--- .../mixins/base/scratchVersioning/index.ts | 18 ++++++++++++++++++ 4 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 extensions/src/common/extension/mixins/base/scratchVersioning/index.ts diff --git a/extensions/src/common/extension/index.ts b/extensions/src/common/extension/index.ts index d9a36ba6d..99f42349a 100644 --- a/extensions/src/common/extension/index.ts +++ b/extensions/src/common/extension/index.ts @@ -1,6 +1,7 @@ import { ExtensionWithFunctionality, MixinName, optionalMixins } from "./mixins/index"; import { ExtensionBase } from "./ExtensionBase"; import scratchInfo from "./mixins/base/scratchInfo"; +import scratchVersions from "./mixins/base/scratchVersioning"; import supported from "./mixins/base/supported"; import { ExtensionMenuDisplayDetails, Writeable } from "$common/types"; import { tryCaptureDependencies } from "./mixins/dependencies"; @@ -49,7 +50,7 @@ export const extension = ( if (details) extensionBundleEvent?.fire({ details, addOns }); - const Base = scratchInfo(supported(ExtensionBase, addOns)) as ExtensionWithFunctionality<[...TSupported]>; + const Base = scratchVersions(scratchInfo(supported(ExtensionBase, addOns))) as ExtensionWithFunctionality<[...TSupported]>; if (!addOns) return Base; diff --git a/extensions/src/common/extension/mixins/base/index.ts b/extensions/src/common/extension/mixins/base/index.ts index 18d2d9755..84e6fe57c 100644 --- a/extensions/src/common/extension/mixins/base/index.ts +++ b/extensions/src/common/extension/mixins/base/index.ts @@ -1,8 +1,12 @@ import scratchInfo from "./scratchInfo/index"; +import scratchVersioning from "./scratchVersioning/index"; import supported from "./supported"; export type CustomizableExtensionConstructor = ReturnType; export type CustomizableExtensionInstance = InstanceType -export type MinimalExtensionConstructor = ReturnType; -export type MinimalExtensionInstance = InstanceType>; \ No newline at end of file +export type BaseScratchExtensionConstuctor = ReturnType; +export type BaseScratchExtensionInstance = InstanceType; + +export type MinimalExtensionConstructor = ReturnType; +export type MinimalExtensionInstance = InstanceType>; \ No newline at end of file diff --git a/extensions/src/common/extension/mixins/base/scratchInfo/index.ts b/extensions/src/common/extension/mixins/base/scratchInfo/index.ts index dbab3baf2..9b491f742 100644 --- a/extensions/src/common/extension/mixins/base/scratchInfo/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchInfo/index.ts @@ -9,7 +9,7 @@ import { Handler } from "./handlers"; import { BlockDefinition, getButtonID, isBlockGetter } from "./util"; import { convertToArgumentInfo, extractArgs, zipArgs } from "./args"; import { convertToDisplayText } from "./text"; -import { CustomizableExtensionConstructor, MinimalExtensionInstance, } from ".."; +import { CustomizableExtensionConstructor, BaseScratchExtensionInstance, } from ".."; import { ExtensionInstanceWithFunctionality } from "../.."; import { blockIDKey } from "$common/globals"; @@ -29,7 +29,7 @@ const checkForBlockContext = (blockUtility: BlockUtilityWithID) => isBlockUtilit * @param args The args that must be parsed before being passed to the underlying operation * @returns */ -export const wrapOperation = ( +export const wrapOperation = ( _this: T, operation: BlockOperation, args: { name: string, type: ValueOf, handler: Handler }[] @@ -126,7 +126,7 @@ export default function (Ctor: CustomizableExtensionConstructor) { info.func = buttonID; } else { const implementationName = getImplementationName(opcode); - this[implementationName] = wrapOperation(this as MinimalExtensionInstance, operation, zipArgs(args)); + this[implementationName] = wrapOperation(this as BaseScratchExtensionInstance, operation, zipArgs(args)); } return info; diff --git a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts new file mode 100644 index 000000000..91d3b82fc --- /dev/null +++ b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts @@ -0,0 +1,18 @@ +import { BaseScratchExtensionConstuctor } from ".."; + +/** + * Mixin the ability for extensions to check which optional mixins they support + * @param Ctor + * @returns + * @see https://www.typescriptlang.org/docs/handbook/mixins.html + */ +export default function (Ctor: BaseScratchExtensionConstuctor) { + abstract class ExtensionWithConfigurableSupport extends Ctor { + + pushVersions(opcode: string, versions: any) { + + } + } + + return ExtensionWithConfigurableSupport; +} \ No newline at end of file From d8a0c179fbb1f6336836c8fd2c18274f6573e27a Mon Sep 17 00:00:00 2001 From: "Github Action (authored by pmalacho-mit)" Date: Tue, 25 Jun 2024 17:21:06 -0700 Subject: [PATCH 02/24] updating comment --- .../common/extension/mixins/base/scratchVersioning/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts index 91d3b82fc..a15d3a89d 100644 --- a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts @@ -1,7 +1,8 @@ import { BaseScratchExtensionConstuctor } from ".."; /** - * Mixin the ability for extensions to check which optional mixins they support + * Mixin the ability for extensions to have their blocks 'versioned', + * so that projects serialized with past versions of blocks can be loaded. * @param Ctor * @returns * @see https://www.typescriptlang.org/docs/handbook/mixins.html From 2e407db808d058dd185e8eee4303337b4c8cf00e Mon Sep 17 00:00:00 2001 From: ymayarajan3 Date: Thu, 27 Jun 2024 12:58:36 -0400 Subject: [PATCH 03/24] progress --- .../src/common/extension/decorators/blocks.ts | 19 +- .../mixins/base/scratchInfo/index.ts | 17 +- .../mixins/base/scratchVersioning/index.ts | 496 +++++++++++++++++- .../src/common/types/framework/blocks.ts | 22 + 4 files changed, 551 insertions(+), 3 deletions(-) diff --git a/extensions/src/common/extension/decorators/blocks.ts b/extensions/src/common/extension/decorators/blocks.ts index af11b54a9..7ecbf833f 100644 --- a/extensions/src/common/extension/decorators/blocks.ts +++ b/extensions/src/common/extension/decorators/blocks.ts @@ -1,7 +1,7 @@ import type BlockUtility from "$scratch-vm/engine/block-utility"; import { TypedClassDecorator, TypedGetterDecorator, TypedMethodDecorator, TypedSetterDecorator } from "."; import { BlockType } from "$common/types/enums"; -import { BlockMetadata, ScratchArgument, Argument, NoArgsBlock } from "$common/types"; +import { BlockMetadata, ScratchArgument, Argument, NoArgsBlock, VersionedOptions } from "$common/types"; import { getImplementationName } from "../mixins/base/scratchInfo/index"; import { ExtensionInstance } from ".."; import { isFunction, isString, tryCreateBundleTimeEvent } from "$common/utils"; @@ -87,6 +87,23 @@ export function block< }; } +export function versions< + const This extends ExtensionInstance, + const Args extends any[], + const Return, + const Fn extends (...args: Args) => Return, +> + ( + config: VersionedOptions[] + ): TypedMethodDecorator Return> { + + return function (this: This, target: (this: This, ...args: Args) => Return, context: ClassMethodDecoratorContext) { + context.addInitializer(function () { this.pushVersions(target.name, config); }); + // + return target; + }; +} + /** * This is a short-hand for invoking the block decorator when your `blockType` is button * @param text diff --git a/extensions/src/common/extension/mixins/base/scratchInfo/index.ts b/extensions/src/common/extension/mixins/base/scratchInfo/index.ts index 9b491f742..72b226bc6 100644 --- a/extensions/src/common/extension/mixins/base/scratchInfo/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchInfo/index.ts @@ -1,7 +1,7 @@ import { castToType } from "$common/cast"; import CustomArgumentManager from "$common/extension/mixins/configurable/customArguments/CustomArgumentManager"; import { ArgumentType, BlockType } from "$common/types/enums"; -import { BlockOperation, ValueOf, Menu, ExtensionMetadata, ExtensionBlockMetadata, ExtensionMenuMetadata, DynamicMenu, BlockMetadata, BlockUtilityWithID, } from "$common/types"; +import { BlockOperation, ValueOf, Menu, ExtensionMetadata, ExtensionBlockMetadata, ExtensionMenuMetadata, DynamicMenu, BlockMetadata, BlockUtilityWithID, VersionedOptions } from "$common/types"; import { registerButtonCallback } from "$common/ui"; import { isString, typesafeCall, } from "$common/utils"; import { menuProbe, asStaticMenu, getMenuName, convertMenuItemsToString } from "./menus"; @@ -71,8 +71,10 @@ export const wrapOperation = ( export default function (Ctor: CustomizableExtensionConstructor) { type BlockEntry = { definition: BlockDefinition, operation: BlockOperation }; type BlockMap = Map; + type VersionMap = Map; abstract class ScratchExtension extends Ctor { private readonly blockMap: BlockMap = new Map(); + private readonly versionMap: VersionMap = new Map(); private readonly menus: Menu[] = []; private info: ExtensionMetadata; @@ -88,6 +90,19 @@ export default function (Ctor: CustomizableExtensionConstructor) { this.blockMap.set(opcode, { definition, operation } as BlockEntry); } + pushVersions(opcode: string, versions: any) { + if (this.versionMap.has(opcode)) throw new Error(`Attempt to push block with opcode ${opcode}, but it was already set. This is assumed to be a mistake.`) + this.versionMap.set(opcode, versions); + } + + getVersion(opcode: string) { + return this.versionMap.get(opcode); + } + + getVersionMap() { + return this.versionMap; + } + protected getInfo(): ExtensionMetadata { if (!this.info) { const { id, name, blockIconURI } = this; diff --git a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts index a15d3a89d..9d6301c66 100644 --- a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts @@ -7,12 +7,506 @@ import { BaseScratchExtensionConstuctor } from ".."; * @returns * @see https://www.typescriptlang.org/docs/handbook/mixins.html */ + +type BlockType = "reporter" | "command"; // etc, use actual version +type ArgValue = any; +type ArgIdentifier = string | number; +type ArgEntry = { + /** If no id is provided, we can assume that the associated value does not correspond to any previously serialized argument */ + readonly id?: ArgIdentifier, + value: ArgValue +} + + + +type VersionArgTransformMechanism = { + arg: (identifier: ArgIdentifier) => ArgEntry, + args: () => ArgEntry[] +} + +type VersionedArgTransformer = (mechanism: VersionArgTransformMechanism) => ArgEntry[]; + +type VersionedOptions = { + transform?: VersionedArgTransformer; + previousType?: BlockType; + previousName?: string; +}; + +// Constants referring to 'primitive' blocks that are usually shadows, +// or in the case of variables and lists, appear quite often in projects +// math_number +const MATH_NUM_PRIMITIVE = 4; // there's no reason these constants can't collide +// math_positive_number +const POSITIVE_NUM_PRIMITIVE = 5; // with the above, but removing duplication for clarity +// math_whole_number +const WHOLE_NUM_PRIMITIVE = 6; +// math_integer +const INTEGER_NUM_PRIMITIVE = 7; +// math_angle +const ANGLE_NUM_PRIMITIVE = 8; +// colour_picker +const COLOR_PICKER_PRIMITIVE = 9; +// text +const TEXT_PRIMITIVE = 10; +// event_broadcast_menu +const BROADCAST_PRIMITIVE = 11; +// data_variable +const VAR_PRIMITIVE = 12; +// data_listcontents +const LIST_PRIMITIVE = 13; + +// Map block opcodes to the above primitives and the name of the field we can use +// to find the value of the field +const primitiveOpcodeInfoMap = { + math_number: [MATH_NUM_PRIMITIVE, 'NUM'], + math_positive_number: [POSITIVE_NUM_PRIMITIVE, 'NUM'], + math_whole_number: [WHOLE_NUM_PRIMITIVE, 'NUM'], + math_integer: [INTEGER_NUM_PRIMITIVE, 'NUM'], + math_angle: [ANGLE_NUM_PRIMITIVE, 'NUM'], + colour_picker: [COLOR_PICKER_PRIMITIVE, 'COLOUR'], + text: [TEXT_PRIMITIVE, 'TEXT'], + event_broadcast_menu: [BROADCAST_PRIMITIVE, 'BROADCAST_OPTION'], + data_variable: [VAR_PRIMITIVE, 'VARIABLE'], + data_listcontents: [LIST_PRIMITIVE, 'LIST'] +}; + + +const soup_ = '!#%()*+,-./:;=?@[]^_`{|}~' + + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + +/** + * Generate a unique ID, from Blockly. This should be globally unique. + * 87 characters ^ 20 length > 128 bits (better than a UUID). + * @return {string} A globally unique ID string. + */ +const uid = function () { + const length = 20; + const soupLength = soup_.length; + const id = []; + for (let i = 0; i < length; i++) { + id[i] = soup_.charAt(Math.random() * soupLength); + } + return id.join(''); +}; + +type VersionMap = Map; export default function (Ctor: BaseScratchExtensionConstuctor) { abstract class ExtensionWithConfigurableSupport extends Ctor { + + //private readonly versionMap: VersionMap = new Map(); + + + + + + // pushVersions(opcode: string, versions: any) { + // if (this.versionMap.has(opcode)) throw new Error(`Attempt to push block with opcode ${opcode}, but it was already set. This is assumed to be a mistake.`) + // this.versionMap.set(opcode, versions); + // } + + alterProjectJSON(blocks, block, version) { + console.log("inside"); + const blocksInfo = this.getInfo().blocks.reduce((acc, tempBlock: any) => { + acc[tempBlock.opcode] = tempBlock; + return acc; + }, {}); + let blockInfoIndex = block.opcode.replace(`${block.opcode.split("_")[0]}_`, ""); + let oldIndex = blockInfoIndex; + const nameMap = this.createNameMap(blocksInfo); + if (nameMap[version] && nameMap[version][blockInfoIndex]) { + blockInfoIndex = nameMap[version][blockInfoIndex]; + } + console.log(block.opcode); + block.opcode = block.opcode.replace(oldIndex, blockInfoIndex); + console.log(block.opcode); + const versions = this.getVersion(blockInfoIndex); + console.log(this.getVersionMap()); + console.log(versions); + if (versions && version < versions.length) { + const blockArgs = this.removeImageEntries(blocksInfo[blockInfoIndex].arguments); + var { inputs, variables } = this.gatherInputs(blocks, block); + var fields = this.gatherFields(block, blockArgs); + console.log(inputs); + console.log(fields); + var totalList = this.addInputsAndFields(inputs, fields); + console.log(totalList); + const newInputs = {}; + const newFields = {}; + let changed = false; + let moveToSay = false; + for (let i = version; i < versions.length; i++) { + if (versions[i].transform) { + const map = new Map(); + for (let i = 0; i < totalList.length; i++) { + map.set(totalList[i].id, totalList[i]); + } + const mechanism: VersionArgTransformMechanism = { + arg: (identifier: ArgIdentifier) => map.get(identifier), + args: () => Array.from(map.values()), + } + console.log(versions[i].transform); + console.log(mechanism); + const newEntries: ArgEntry[] = versions[i].transform(mechanism); + console.log(newEntries); + const { entries, mappings } = this.updateEntries(newEntries); + totalList = entries; + console.log("list"); + console.log(totalList); + variables = this.updateDictionary(variables, mappings) + } + if (versions[i].previousType) { + if (versions[i].previousType == "reporter" && blocksInfo[blockInfoIndex].blockType == "command") { // reporter to command + changed = !changed; + if (moveToSay) { + moveToSay = false; + } + } else { // command to reporter + changed = !changed; + if (!moveToSay) { + moveToSay = true; + } + } + } + } + + for (let i = 0; i < Object.keys(blockArgs).length; i++) { + const argIndex = Object.keys(blockArgs)[i]; + if (Object.keys(variables).includes(argIndex)) { + newInputs[argIndex] = variables[argIndex]; + } else if (blockArgs[argIndex].menu) { + var fieldValue = totalList[argIndex]; + if (typeof fieldValue == "number") { + fieldValue = String(fieldValue); + } + newFields[argIndex] = { + name: String(argIndex), + value: fieldValue, + id: null + } + } else { + const values = this.createInputBlock(blocks, blockArgs[argIndex].type, totalList[argIndex], block.id); + const primitiveId = values.newId; + blocks = values.blocks; + console.log(primitiveOpcodeInfoMap[blocks[primitiveId].opcode][0]); + newInputs[argIndex] = [ + 1, [ + primitiveOpcodeInfoMap[blocks[primitiveId].opcode][0], + String(totalList[argIndex].value) + ] + ]; + } + + } + + // Re-assign fields and inputs + block.inputs = newInputs; + console.log(newInputs); + block.fields = newFields; + blocks[block.id] = block; + const regex = /_v(\d+)/g; + + if (moveToSay && changed) { + const oldID = block.id; + const next = block.next; + block.id = uid(); + //blockJSON.topLevel = false; + const newBlock = Object.create(null); + newBlock.id = oldID; + newBlock.parent = block.parent; + block.parent = newBlock.id; + newBlock.fields = {}; + newBlock.inputs = { + MESSAGE: { + name: 'MESSAGE', + block: block.id, + shadow: block.id + } + } + newBlock.next = next; + block.next = null; + newBlock.opcode = "looks_say"; + newBlock.shadow = false; + //newBlock.topLevel = true; + for (const key of Object.keys(block.inputs)) { + if (block.inputs[key].block) { + let inputBlock = block.inputs[key].block; + if (blocks[inputBlock]) { + blocks[inputBlock].parent = block.id; + block.inputs[key].shadow = block.inputs[key].block; + } + + } + } + blocks[newBlock.id] = newBlock; + blocks[block.id] = block; + } else if (!moveToSay && changed) { + if (blocks[block.parent]) { + const parentBlock = blocks[block.parent]; + if (parentBlock) { + let parentIndex = parentBlock.opcode; + parentIndex = parentIndex.replace(`${parentIndex.split("_")[0]}_`, ""); + parentIndex = parentIndex.replace(regex, ""); + let argInfo = blocksInfo[parentIndex].arguments; + const values = this.removeInput(parentBlock, block, blocks, argInfo); + blocks[parentBlock.id] = values.block; + blocks = values.blocks; + block.parent = null; + block.topLevel = true; + } + + } + } + blocks[block.id] = block; + + } + // step 5: get extension version info + // step 6: remove image entries from arguments + // step 7: combine inputs and fields + // step 8: apply transform functions + // step 9: say blocks + return blocks; + } + + updateEntries(entries) { + const mappings = {}; + for (let i = 0; i < entries.length; i++) { + mappings[entries[i].id] = String(i); + entries[i].id = String(i); + } + return { entries, mappings }; + + } + + getNewIds(blockJSON) { + console.log("here"); + console.log(blockJSON); + let ids = []; + const inputs = blockJSON.inputs; + console.log(inputs); + for (const input of Object.keys(inputs)) { + console.log(inputs[input]); + if (inputs[input].block) { + ids.push(inputs[input].block); + } + } + return ids; + } - pushVersions(opcode: string, versions: any) { + createInputBlock(blocks, type, value, parentId) { + value = String(value); + const primitiveObj = Object.create(null); + const newId = uid(); + primitiveObj.id = newId; + primitiveObj.next = null; + primitiveObj.parent = parentId; + primitiveObj.shadow = true; + primitiveObj.inputs = Object.create(null); + // need a reference to parent id + switch (type) { + case "number": { + if (value == "undefined") { + value = 0; + } + primitiveObj.opcode = 'math_number'; + primitiveObj.fields = { + NUM: { + name: 'NUM', + value: value + } + }; + primitiveObj.topLevel = false; + break; + } + case "angle": { + if (value == "undefined") { + value = 0; + } + primitiveObj.opcode = 'math_angle'; + primitiveObj.fields = { + NUM: { + name: 'NUM', + value: value + } + }; + primitiveObj.topLevel = false; + break; + } + case "color": { + if (value == "undefined") { + value = 0; + } + primitiveObj.opcode = 'colour_picker'; + primitiveObj.fields = { + COLOUR: { + name: 'COLOUR', + value: value + } + }; + primitiveObj.topLevel = false; + break; + } + case "string": { + primitiveObj.opcode = 'text'; + primitiveObj.fields = { + TEXT: { + name: 'TEXT', + value: value + } + }; + primitiveObj.topLevel = false; + break; + } + default: { + //log.error(`Found unknown primitive type during deserialization: ${JSON.stringify(inputDescOrId)}`); + return null; + } + } + blocks[newId] = primitiveObj; + return { newId, blocks }; + }; + createNameMap(blocksInfo) { + const versionMap = new Map(); + for (const opcode of Object.keys(blocksInfo)) { + if (typeof blocksInfo[opcode].versions == "object" && Object.keys(blocksInfo[opcode].versions).length > 0) { + let tempName = opcode; + let versions = blocksInfo[opcode].versions; + for (let index = versions.length - 1; index >= 0; index--) { // loop through each version entry + if (!versionMap.has(index)) { + versionMap[index] = {}; + } + const version = versions[index]; + if (typeof version == "object" && version.previousName) { // check if the version entry has a name + const oldName = version.previousName; + tempName = oldName; + } + if (tempName != opcode) { + versionMap[index][tempName] = opcode; + } + } + } + } + return versionMap; } + + + /** + * Remove image entries from the passed-in arguments for each block from the extension + * @param {object} dict The arguments of the block + * @return {object} The arguments of the block with the static images removed + */ + removeImageEntries(dict: any) { + const filteredDict = {}; + + for (const [key, value] of Object.entries(dict)) { + if ((value as any).type !== 'image') { + filteredDict[key] = value; + } + } + + return filteredDict; + } + + /** + * Gather the primitive values from the 'inputs' property of a block as well as the block's variables + * + * @param {object} blocks The blocks related to the Scratch object + * @param {object} blockJSON The block to gather the inputs from + * @return {object} return.inputs - A dictionary of the inputs, with each value a primitive object + * @return {object} return.variables - A dictionary with all th block's variables as values and their positions as keys + */ + gatherInputs(blocks, blockJSON) { + var inputs = {}; + var variables = {}; + const args: ArgEntry[] = []; + if (blockJSON.inputs && Object.keys(blockJSON.inputs).length > 0) { + Object.keys(blockJSON.inputs).forEach(input => { + var keyIndex = input; + input = blockJSON.inputs[input]; + if (blocks[(input as any).block]) { + if (Object.keys(primitiveOpcodeInfoMap).includes(blocks[(input as any).block].opcode)) { + const inputBlock = blocks[(input as any).block].fields; + const inputType = Object.keys(blocks[(input as any).block].fields)[0]; + var inputValue = inputBlock[inputType].value; + if (inputType == "NUM") { + inputValue = parseFloat(inputValue); + } + } else { + variables[keyIndex] = input; + inputValue = "0"; + } + args.push({id: keyIndex, value: inputValue}) + } + }) + } + return { inputs: args, variables: variables }; + } + + gatherFields(blockJSON, argList) { + const args: ArgEntry[] = []; + var fields = {}; + if (blockJSON.fields && Object.keys(blockJSON.fields).length > 0) { + Object.keys(blockJSON.fields).forEach(field => { + const keyIndex = field; + field = blockJSON.fields[field]; + var value = (field as any).value; + var argType = argList[(field as any).name].type; + if (argType == "number" || argType == "angle") { + value = parseFloat(value); + } + args.push({id: keyIndex, value: value}) + }) + } + return args; + } + + /** + * A function that combines the primitive values from the block's inputs and fields + * so that it can be processed by the version functions + * + * @param {object} inputs A dictionary with the primitive values of the block's inputs + * @param {object} fields A dictionary with the primitive values of the block's fields + * @param {object} argList The argument dictionary for the associated block + * @return {Array} An array with the combined primitive values from the block's inputs and fields + */ + addInputsAndFields(inputs, fields) { + return inputs.concat(fields); + } + + updateDictionary(originalDict, keyMapping) { + const updatedDict = {}; + for (const [oldKey, newKey] of Object.entries(keyMapping)) { + if (originalDict.hasOwnProperty(oldKey)) { + const newEntry = { ...originalDict[oldKey] }; + newEntry.name = String(newKey); // Update the name field + updatedDict[newKey as any] = newEntry; + } + } + return updatedDict; + } + + removeInput(block, removeBlock, blocks, argInfo) { + const inputs = block.inputs; + const newInputs = {}; + let objectBlocks = {}; + for (const key of Object.keys(inputs)) { + if (inputs[key] && inputs[key].block == removeBlock.id) { + const values = this.createInputBlock(blocks, argInfo[key].type, argInfo[key].defaultValue, block.id); + objectBlocks = values.blocks; + newInputs[key] = { + name: String(key), + block: values.newId, + shadow: values.newId, + }; + } else { + newInputs[key] = inputs[key]; + } + } + block.inputs = newInputs; + return { block: block, blocks: objectBlocks}; + } + + } return ExtensionWithConfigurableSupport; diff --git a/extensions/src/common/types/framework/blocks.ts b/extensions/src/common/types/framework/blocks.ts index 3dbfa3d6b..a4782f653 100644 --- a/extensions/src/common/types/framework/blocks.ts +++ b/extensions/src/common/types/framework/blocks.ts @@ -277,6 +277,28 @@ type Operation = export type ScratchBlockType = typeof BlockType[keyof typeof BlockType]; +export type BlockType = "reporter" | "command"; // etc, use actual version +export type ArgValue = any; +export type ArgIdentifier = string | number; +export type ArgEntry = { + /** If no id is provided, we can assume that the associated value does not correspond to any previously serialized argument */ + readonly id?: ArgIdentifier, + value: ArgValue +} + +export type VersionArgTransformMechanism = { + arg: (identifier: ArgIdentifier) => ArgEntry, + args: () => ArgEntry[] +} + +export type VersionedArgTransformer = (mechanism: VersionArgTransformMechanism) => ArgEntry[]; + +export type VersionedOptions = { +transform?: VersionedArgTransformer; +previousType?: BlockType; +previousName?: string; +}; + export type ReturnTypeByBlockType> = T extends typeof BlockType.Boolean ? boolean From eb60b60d8c0399cef82800f9450daab4e1889fe2 Mon Sep 17 00:00:00 2001 From: ymayarajan3 Date: Thu, 27 Jun 2024 13:27:49 -0400 Subject: [PATCH 04/24] name fix --- .../common/extension/mixins/base/scratchVersioning/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts index 9d6301c66..33cb92be9 100644 --- a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts @@ -113,6 +113,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { let blockInfoIndex = block.opcode.replace(`${block.opcode.split("_")[0]}_`, ""); let oldIndex = blockInfoIndex; const nameMap = this.createNameMap(blocksInfo); + console.log(nameMap); if (nameMap[version] && nameMap[version][blockInfoIndex]) { blockInfoIndex = nameMap[version][blockInfoIndex]; } @@ -368,9 +369,9 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { createNameMap(blocksInfo) { const versionMap = new Map(); for (const opcode of Object.keys(blocksInfo)) { - if (typeof blocksInfo[opcode].versions == "object" && Object.keys(blocksInfo[opcode].versions).length > 0) { + const versions = this.getVersion(opcode); + if (versions && versions.length > 0) { let tempName = opcode; - let versions = blocksInfo[opcode].versions; for (let index = versions.length - 1; index >= 0; index--) { // loop through each version entry if (!versionMap.has(index)) { versionMap[index] = {}; From b76da160a9f3e74d7acc7c33e09471a1f8366664 Mon Sep 17 00:00:00 2001 From: ymayarajan3 Date: Thu, 27 Jun 2024 15:03:11 -0400 Subject: [PATCH 05/24] say block support --- .../mixins/base/scratchVersioning/index.ts | 49 +++++++------------ 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts index 33cb92be9..40084f338 100644 --- a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts @@ -104,8 +104,8 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { // this.versionMap.set(opcode, versions); // } - alterProjectJSON(blocks, block, version) { - console.log("inside"); + alterProjectJSON(blocks, block, version, blockLib) { + var addIds = []; const blocksInfo = this.getInfo().blocks.reduce((acc, tempBlock: any) => { acc[tempBlock.opcode] = tempBlock; return acc; @@ -117,20 +117,13 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { if (nameMap[version] && nameMap[version][blockInfoIndex]) { blockInfoIndex = nameMap[version][blockInfoIndex]; } - console.log(block.opcode); block.opcode = block.opcode.replace(oldIndex, blockInfoIndex); - console.log(block.opcode); const versions = this.getVersion(blockInfoIndex); - console.log(this.getVersionMap()); - console.log(versions); if (versions && version < versions.length) { const blockArgs = this.removeImageEntries(blocksInfo[blockInfoIndex].arguments); var { inputs, variables } = this.gatherInputs(blocks, block); var fields = this.gatherFields(block, blockArgs); - console.log(inputs); - console.log(fields); var totalList = this.addInputsAndFields(inputs, fields); - console.log(totalList); const newInputs = {}; const newFields = {}; let changed = false; @@ -145,14 +138,9 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { arg: (identifier: ArgIdentifier) => map.get(identifier), args: () => Array.from(map.values()), } - console.log(versions[i].transform); - console.log(mechanism); const newEntries: ArgEntry[] = versions[i].transform(mechanism); - console.log(newEntries); const { entries, mappings } = this.updateEntries(newEntries); totalList = entries; - console.log("list"); - console.log(totalList); variables = this.updateDictionary(variables, mappings) } if (versions[i].previousType) { @@ -185,28 +173,27 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { id: null } } else { - const values = this.createInputBlock(blocks, blockArgs[argIndex].type, totalList[argIndex], block.id); + const values = this.createInputBlock(blocks, blockArgs[argIndex].type, totalList[argIndex].value, block.id, blockLib); const primitiveId = values.newId; blocks = values.blocks; - console.log(primitiveOpcodeInfoMap[blocks[primitiveId].opcode][0]); - newInputs[argIndex] = [ - 1, [ - primitiveOpcodeInfoMap[blocks[primitiveId].opcode][0], - String(totalList[argIndex].value) - ] - ]; + newInputs[argIndex] = { + name: String(argIndex), + block: values.newId, + shadow: values.newId + + } } } // Re-assign fields and inputs block.inputs = newInputs; - console.log(newInputs); block.fields = newFields; blocks[block.id] = block; const regex = /_v(\d+)/g; if (moveToSay && changed) { + console.log("new block"); const oldID = block.id; const next = block.next; block.id = uid(); @@ -238,6 +225,8 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { } } + blockLib.createBlock(newBlock); + blockLib.createBlock(block); blocks[newBlock.id] = newBlock; blocks[block.id] = block; } else if (!moveToSay && changed) { @@ -248,7 +237,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { parentIndex = parentIndex.replace(`${parentIndex.split("_")[0]}_`, ""); parentIndex = parentIndex.replace(regex, ""); let argInfo = blocksInfo[parentIndex].arguments; - const values = this.removeInput(parentBlock, block, blocks, argInfo); + const values = this.removeInput(parentBlock, block, blocks, argInfo, blockLib); blocks[parentBlock.id] = values.block; blocks = values.blocks; block.parent = null; @@ -260,11 +249,6 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { blocks[block.id] = block; } - // step 5: get extension version info - // step 6: remove image entries from arguments - // step 7: combine inputs and fields - // step 8: apply transform functions - // step 9: say blocks return blocks; } @@ -293,7 +277,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { return ids; } - createInputBlock(blocks, type, value, parentId) { + createInputBlock(blocks, type, value, parentId, blockLib) { value = String(value); const primitiveObj = Object.create(null); const newId = uid(); @@ -363,6 +347,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { } } blocks[newId] = primitiveObj; + blockLib.createBlock(primitiveObj); return { newId, blocks }; }; @@ -486,13 +471,13 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { return updatedDict; } - removeInput(block, removeBlock, blocks, argInfo) { + removeInput(block, removeBlock, blocks, argInfo, blockLib) { const inputs = block.inputs; const newInputs = {}; let objectBlocks = {}; for (const key of Object.keys(inputs)) { if (inputs[key] && inputs[key].block == removeBlock.id) { - const values = this.createInputBlock(blocks, argInfo[key].type, argInfo[key].defaultValue, block.id); + const values = this.createInputBlock(blocks, argInfo[key].type, argInfo[key].defaultValue, block.id, blockLib); objectBlocks = values.blocks; newInputs[key] = { name: String(key), From 66af338e5553ce84dd37e68ee3856d666e8d8ee6 Mon Sep 17 00:00:00 2001 From: ymayarajan3 Date: Thu, 27 Jun 2024 22:34:49 -0400 Subject: [PATCH 06/24] moving to project json --- .../mixins/base/scratchVersioning/index.ts | 591 ++++++++++++------ .../src/common/types/framework/blocks.ts | 4 +- 2 files changed, 417 insertions(+), 178 deletions(-) diff --git a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts index 40084f338..b4db74bc7 100644 --- a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts @@ -32,6 +32,21 @@ type VersionedOptions = { previousName?: string; }; +const CORE_EXTENSIONS = [ + 'argument', + 'colour', + 'control', + 'data', + 'event', + 'looks', + 'math', + 'motion', + 'operator', + 'procedures', + 'sensing', + 'sound' +]; + // Constants referring to 'primitive' blocks that are usually shadows, // or in the case of variables and lists, appear quite often in projects // math_number @@ -93,164 +108,352 @@ type VersionMap = Map; export default function (Ctor: BaseScratchExtensionConstuctor) { abstract class ExtensionWithConfigurableSupport extends Ctor { - //private readonly versionMap: VersionMap = new Map(); - + alterJSON(projectJSON) { + const targetObjects = projectJSON.targets + .map((t, i) => Object.assign(t, { targetPaneOrder: i })) + .sort((a, b) => a.layerOrder - b.layerOrder); + //const targetObjects = projectJSON.targets; + const newTargets = []; + for (const object of targetObjects) { + const newBlocks = {}; + for (const blockId in object.blocks) { + + let blockJSON = object.blocks[blockId]; + let version = 0; + const blockOpcode = blockJSON.opcode; - + // Check if version name is included + const regex = /_v(\d+)/g; + const matches = blockOpcode.match(regex); // Get all matches - // pushVersions(opcode: string, versions: any) { - // if (this.versionMap.has(opcode)) throw new Error(`Attempt to push block with opcode ${opcode}, but it was already set. This is assumed to be a mistake.`) - // this.versionMap.set(opcode, versions); - // } + if (matches) { + const lastMatch = matches[matches.length - 1]; + const versionMatch = lastMatch.match(/_v(\d+)/); - alterProjectJSON(blocks, block, version, blockLib) { - var addIds = []; - const blocksInfo = this.getInfo().blocks.reduce((acc, tempBlock: any) => { - acc[tempBlock.opcode] = tempBlock; - return acc; - }, {}); - let blockInfoIndex = block.opcode.replace(`${block.opcode.split("_")[0]}_`, ""); - let oldIndex = blockInfoIndex; - const nameMap = this.createNameMap(blocksInfo); - console.log(nameMap); - if (nameMap[version] && nameMap[version][blockInfoIndex]) { - blockInfoIndex = nameMap[version][blockInfoIndex]; - } - block.opcode = block.opcode.replace(oldIndex, blockInfoIndex); - const versions = this.getVersion(blockInfoIndex); - if (versions && version < versions.length) { - const blockArgs = this.removeImageEntries(blocksInfo[blockInfoIndex].arguments); - var { inputs, variables } = this.gatherInputs(blocks, block); - var fields = this.gatherFields(block, blockArgs); - var totalList = this.addInputsAndFields(inputs, fields); - const newInputs = {}; - const newFields = {}; - let changed = false; - let moveToSay = false; - for (let i = version; i < versions.length; i++) { - if (versions[i].transform) { - const map = new Map(); - for (let i = 0; i < totalList.length; i++) { - map.set(totalList[i].id, totalList[i]); - } - const mechanism: VersionArgTransformMechanism = { - arg: (identifier: ArgIdentifier) => map.get(identifier), - args: () => Array.from(map.values()), - } - const newEntries: ArgEntry[] = versions[i].transform(mechanism); - const { entries, mappings } = this.updateEntries(newEntries); - totalList = entries; - variables = this.updateDictionary(variables, mappings) - } - if (versions[i].previousType) { - if (versions[i].previousType == "reporter" && blocksInfo[blockInfoIndex].blockType == "command") { // reporter to command - changed = !changed; - if (moveToSay) { - moveToSay = false; - } - } else { // command to reporter - changed = !changed; - if (!moveToSay) { - moveToSay = true; - } + if (versionMatch) { + version = parseInt(versionMatch[1], 10); // Extract and parse the version number } + blockJSON.opcode = blockOpcode.replace(regex, ""); // Remove all version numbers from the opcode } - } - for (let i = 0; i < Object.keys(blockArgs).length; i++) { - const argIndex = Object.keys(blockArgs)[i]; - if (Object.keys(variables).includes(argIndex)) { - newInputs[argIndex] = variables[argIndex]; - } else if (blockArgs[argIndex].menu) { - var fieldValue = totalList[argIndex]; - if (typeof fieldValue == "number") { - fieldValue = String(fieldValue); + const extensionID = this.getExtensionIdForOpcode(blockJSON.opcode); + const blocksInfo = this.getInfo().blocks.reduce((acc, tempBlock: any) => { + acc[tempBlock.opcode] = tempBlock; + return acc; + }, {}); + const menuRegex = /menu_\d+/g; + if (extensionID == this.getInfo().id) { + const block = object.blocks[blockId]; + let blockInfoIndex = block.opcode.replace(`${block.opcode.split("_")[0]}_`, ""); + let oldIndex = blockInfoIndex; + const nameMap = this.createNameMap(blocksInfo); + console.log(nameMap); + if (nameMap[version] && nameMap[version][blockInfoIndex]) { + blockInfoIndex = nameMap[version][blockInfoIndex]; } - newFields[argIndex] = { - name: String(argIndex), - value: fieldValue, - id: null + block.opcode = block.opcode.replace(oldIndex, blockInfoIndex); + const versions = this.getVersion(blockInfoIndex); + if (versions && version < versions.length) { + const blockArgs = this.removeImageEntries(blocksInfo[blockInfoIndex].arguments); + var { inputs, variables } = this.gatherInputs(object.blocks, block); + var fields = this.gatherFields(block, blockArgs); + var totalList = this.addInputsAndFields(inputs, fields); + const newInputs = {}; + const newFields = {}; + let changed = false; + let moveToSay = false; + for (let i = version; i < versions.length; i++) { + if (versions[i].transform) { + const map = new Map(); + for (let i = 0; i < totalList.length; i++) { + map.set(totalList[i].id, totalList[i]); + } + const mechanism: VersionArgTransformMechanism = { + arg: (identifier: ArgIdentifier) => map.get(identifier), + args: () => Array.from(map.values()), + } + const newEntries: ArgEntry[] = versions[i].transform(mechanism); + const { entries, mappings } = this.updateEntries(newEntries); + totalList = entries; + variables = this.updateDictionary(variables, mappings) + } + if (versions[i].previousType) { + if (versions[i].previousType == "reporter" && blocksInfo[blockInfoIndex].blockType == "command") { // reporter to command + changed = !changed; + if (moveToSay) { + moveToSay = false; + } + } else { // command to reporter + changed = !changed; + if (!moveToSay) { + moveToSay = true; + } + } + } + } + + for (let i = 0; i < Object.keys(blockArgs).length; i++) { + const argIndex = Object.keys(blockArgs)[i]; + if (Object.keys(variables).includes(argIndex)) { + newInputs[argIndex] = variables[argIndex]; + } else if (blockArgs[argIndex].menu) { + var fieldValue = totalList[argIndex]; + if (typeof fieldValue == "number") { + fieldValue = String(fieldValue); + } + newFields[argIndex] = { + name: String(argIndex), + value: fieldValue, + id: null + } + + } else { + const primitiveBlock = this.createInputBlock(object.blocks, blockArgs[argIndex].type, totalList[argIndex].value, block.id); + newInputs[argIndex] = [ + 1, [ + primitiveOpcodeInfoMap[primitiveBlock.opcode][0], + String(totalList[argIndex].value) + ] + ] + } + + } + + // Re-assign fields and inputs + block.inputs = newInputs; + block.fields = newFields; + const regex = /_v(\d+)/g; + + if (moveToSay && changed) { + console.log("new block"); + const oldID = blockId; + const next = block.next; + block.id = uid(); + //blockJSON.topLevel = false; + const newBlock = Object.create(null); + newBlock.id = oldID; + newBlock.parent = block.parent; + block.parent = newBlock.id; + newBlock.fields = {}; + newBlock.inputs = { + MESSAGE: [ + 3, + block.id, + [ + 10, + "Hello" + ] + ] + } + newBlock.next = next; + block.next = null; + newBlock.opcode = "looks_say"; + newBlock.shadow = false; + //newBlock.topLevel = true; + for (const key of Object.keys(block.inputs)) { + if (block.inputs[key].block) { + let inputBlock = block.inputs[key].block; + if (object.blocks[inputBlock]) { + object.blocks[inputBlock].parent = block.id; + block.inputs[key].shadow = block.inputs[key].block; + } + + } + } + newBlocks[block.id] = block; + newBlocks[newBlock.id] = newBlock; + } else if (!moveToSay && changed) { + if (object.blocks[block.parent]) { + const parentBlock = object.blocks[block.parent]; + if (parentBlock) { + let parentIndex = parentBlock.opcode; + parentIndex = parentIndex.replace(`${parentIndex.split("_")[0]}_`, ""); + parentIndex = parentIndex.replace(regex, ""); + let argInfo = blocksInfo[parentIndex].arguments; + const values = this.removeInput(parentBlock, block, object.blocks, argInfo); + object.blocks[parentBlock.id] = values.block; + object.blocks = values.blocks; + block.parent = null; + block.topLevel = true; + } + + } + } + //object.blocks[block.id] = block; + } - } else { - const values = this.createInputBlock(blocks, blockArgs[argIndex].type, totalList[argIndex].value, block.id, blockLib); - const primitiveId = values.newId; - blocks = values.blocks; - newInputs[argIndex] = { - name: String(argIndex), - block: values.newId, - shadow: values.newId - + if (!Object.keys(newBlocks).includes(blockId)) { + newBlocks[blockId] = block; } - } - + + } } + object.blocks = newBlocks; + newTargets.push(object); + } + projectJSON.targets = newTargets; + return projectJSON; + } - // Re-assign fields and inputs - block.inputs = newInputs; - block.fields = newFields; - blocks[block.id] = block; - const regex = /_v(\d+)/g; - - if (moveToSay && changed) { - console.log("new block"); - const oldID = block.id; - const next = block.next; - block.id = uid(); - //blockJSON.topLevel = false; - const newBlock = Object.create(null); - newBlock.id = oldID; - newBlock.parent = block.parent; - block.parent = newBlock.id; - newBlock.fields = {}; - newBlock.inputs = { - MESSAGE: { - name: 'MESSAGE', - block: block.id, - shadow: block.id - } - } - newBlock.next = next; - block.next = null; - newBlock.opcode = "looks_say"; - newBlock.shadow = false; - //newBlock.topLevel = true; - for (const key of Object.keys(block.inputs)) { - if (block.inputs[key].block) { - let inputBlock = block.inputs[key].block; - if (blocks[inputBlock]) { - blocks[inputBlock].parent = block.id; - block.inputs[key].shadow = block.inputs[key].block; - } + getExtensionIdForOpcode(opcode) { + // Allowed ID characters are those matching the regular expression [\w-]: A-Z, a-z, 0-9, and hyphen ("-"). + const index = opcode.indexOf('_'); + const forbiddenSymbols = /[^\w-]/g; + const prefix = opcode.substring(0, index).replace(forbiddenSymbols, '-'); + if (CORE_EXTENSIONS.indexOf(prefix) === -1) { + if (prefix !== '') return prefix; + } + }; - } - } - blockLib.createBlock(newBlock); - blockLib.createBlock(block); - blocks[newBlock.id] = newBlock; - blocks[block.id] = block; - } else if (!moveToSay && changed) { - if (blocks[block.parent]) { - const parentBlock = blocks[block.parent]; - if (parentBlock) { - let parentIndex = parentBlock.opcode; - parentIndex = parentIndex.replace(`${parentIndex.split("_")[0]}_`, ""); - parentIndex = parentIndex.replace(regex, ""); - let argInfo = blocksInfo[parentIndex].arguments; - const values = this.removeInput(parentBlock, block, blocks, argInfo, blockLib); - blocks[parentBlock.id] = values.block; - blocks = values.blocks; - block.parent = null; - block.topLevel = true; - } + + // alterProjectJSON(blocks, block, version, blockLib) { + // var addIds = []; + // const blocksInfo = this.getInfo().blocks.reduce((acc, tempBlock: any) => { + // acc[tempBlock.opcode] = tempBlock; + // return acc; + // }, {}); + // let blockInfoIndex = block.opcode.replace(`${block.opcode.split("_")[0]}_`, ""); + // let oldIndex = blockInfoIndex; + // const nameMap = this.createNameMap(blocksInfo); + // console.log(nameMap); + // if (nameMap[version] && nameMap[version][blockInfoIndex]) { + // blockInfoIndex = nameMap[version][blockInfoIndex]; + // } + // block.opcode = block.opcode.replace(oldIndex, blockInfoIndex); + // const versions = this.getVersion(blockInfoIndex); + // if (versions && version < versions.length) { + // const blockArgs = this.removeImageEntries(blocksInfo[blockInfoIndex].arguments); + // var { inputs, variables } = this.gatherInputs(blocks, block); + // var fields = this.gatherFields(block, blockArgs); + // var totalList = this.addInputsAndFields(inputs, fields); + // const newInputs = {}; + // const newFields = {}; + // let changed = false; + // let moveToSay = false; + // for (let i = version; i < versions.length; i++) { + // if (versions[i].transform) { + // const map = new Map(); + // for (let i = 0; i < totalList.length; i++) { + // map.set(totalList[i].id, totalList[i]); + // } + // const mechanism: VersionArgTransformMechanism = { + // arg: (identifier: ArgIdentifier) => map.get(identifier), + // args: () => Array.from(map.values()), + // } + // const newEntries: ArgEntry[] = versions[i].transform(mechanism); + // const { entries, mappings } = this.updateEntries(newEntries); + // totalList = entries; + // variables = this.updateDictionary(variables, mappings) + // } + // if (versions[i].previousType) { + // if (versions[i].previousType == "reporter" && blocksInfo[blockInfoIndex].blockType == "command") { // reporter to command + // changed = !changed; + // if (moveToSay) { + // moveToSay = false; + // } + // } else { // command to reporter + // changed = !changed; + // if (!moveToSay) { + // moveToSay = true; + // } + // } + // } + // } + + // for (let i = 0; i < Object.keys(blockArgs).length; i++) { + // const argIndex = Object.keys(blockArgs)[i]; + // if (Object.keys(variables).includes(argIndex)) { + // newInputs[argIndex] = variables[argIndex]; + // } else if (blockArgs[argIndex].menu) { + // var fieldValue = totalList[argIndex]; + // if (typeof fieldValue == "number") { + // fieldValue = String(fieldValue); + // } + // newFields[argIndex] = { + // name: String(argIndex), + // value: fieldValue, + // id: null + // } + // } else { + // const values = this.createInputBlock(blocks, blockArgs[argIndex].type, totalList[argIndex].value, block.id); + // const primitiveId = values.newId; + // blocks = values.blocks; + // newInputs[argIndex] = { + // name: String(argIndex), + // block: values.newId, + // shadow: values.newId + + // } + // } + + // } + + // // Re-assign fields and inputs + // block.inputs = newInputs; + // block.fields = newFields; + // blocks[block.id] = block; + // const regex = /_v(\d+)/g; + + // if (moveToSay && changed) { + // console.log("new block"); + // const oldID = block.id; + // const next = block.next; + // block.id = uid(); + // //blockJSON.topLevel = false; + // const newBlock = Object.create(null); + // newBlock.id = oldID; + // newBlock.parent = block.parent; + // block.parent = newBlock.id; + // newBlock.fields = {}; + // newBlock.inputs = { + // MESSAGE: { + // name: 'MESSAGE', + // block: block.id, + // shadow: block.id + // } + // } + // newBlock.next = next; + // block.next = null; + // newBlock.opcode = "looks_say"; + // newBlock.shadow = false; + // //newBlock.topLevel = true; + // for (const key of Object.keys(block.inputs)) { + // if (block.inputs[key].block) { + // let inputBlock = block.inputs[key].block; + // if (blocks[inputBlock]) { + // blocks[inputBlock].parent = block.id; + // block.inputs[key].shadow = block.inputs[key].block; + // } + + // } + // } + // blockLib.createBlock(newBlock); + // blockLib.createBlock(block); + // blocks[newBlock.id] = newBlock; + // blocks[block.id] = block; + // } else if (!moveToSay && changed) { + // if (blocks[block.parent]) { + // const parentBlock = blocks[block.parent]; + // if (parentBlock) { + // let parentIndex = parentBlock.opcode; + // parentIndex = parentIndex.replace(`${parentIndex.split("_")[0]}_`, ""); + // parentIndex = parentIndex.replace(regex, ""); + // let argInfo = blocksInfo[parentIndex].arguments; + // const values = this.removeInput(parentBlock, block, blocks, argInfo); + // blocks[parentBlock.id] = values.block; + // blocks = values.blocks; + // block.parent = null; + // block.topLevel = true; + // } - } - } - blocks[block.id] = block; + // } + // } + // blocks[block.id] = block; - } - return blocks; - } + // } + // return blocks; + // } updateEntries(entries) { const mappings = {}; @@ -262,22 +465,22 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { } - getNewIds(blockJSON) { - console.log("here"); - console.log(blockJSON); - let ids = []; - const inputs = blockJSON.inputs; - console.log(inputs); - for (const input of Object.keys(inputs)) { - console.log(inputs[input]); - if (inputs[input].block) { - ids.push(inputs[input].block); - } - } - return ids; - } + // getNewIds(blockJSON) { + // console.log("here"); + // console.log(blockJSON); + // let ids = []; + // const inputs = blockJSON.inputs; + // console.log(inputs); + // for (const input of Object.keys(inputs)) { + // console.log(inputs[input]); + // if (inputs[input].block) { + // ids.push(inputs[input].block); + // } + // } + // return ids; + // } - createInputBlock(blocks, type, value, parentId, blockLib) { + createInputBlock(blocks, type, value, parentId) { value = String(value); const primitiveObj = Object.create(null); const newId = uid(); @@ -346,9 +549,9 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { return null; } } - blocks[newId] = primitiveObj; - blockLib.createBlock(primitiveObj); - return { newId, blocks }; + //blocks[newId] = primitiveObj; + //blockLib.createBlock(primitiveObj); + return primitiveObj; }; createNameMap(blocksInfo) { @@ -409,20 +612,56 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { Object.keys(blockJSON.inputs).forEach(input => { var keyIndex = input; input = blockJSON.inputs[input]; - if (blocks[(input as any).block]) { - if (Object.keys(primitiveOpcodeInfoMap).includes(blocks[(input as any).block].opcode)) { - const inputBlock = blocks[(input as any).block].fields; - const inputType = Object.keys(blocks[(input as any).block].fields)[0]; - var inputValue = inputBlock[inputType].value; - if (inputType == "NUM") { - inputValue = parseFloat(inputValue); - } - } else { - variables[keyIndex] = input; - inputValue = "0"; + console.log("INPUT"); + console.log(input); + const type = parseFloat(input[1][0]); + switch (type) { + case 6: + case 5: + case 8: + case 4: { + args.push({id: keyIndex, value: parseFloat(input[1][1])}) + break; } - args.push({id: keyIndex, value: inputValue}) - } + case 10: { + args.push({id: keyIndex, value: input[1][1]}) + break; + } + + } +// const MATH_NUM_PRIMITIVE = 4; // there's no reason these constants can't collide + // // math_positive_number + // const POSITIVE_NUM_PRIMITIVE = 5; // with the above, but removing duplication for clarity + // // math_whole_number + // const WHOLE_NUM_PRIMITIVE = 6; + // // math_integer + // const INTEGER_NUM_PRIMITIVE = 7; + // // math_angle + // const ANGLE_NUM_PRIMITIVE = 8; + // // colour_picker + // const COLOR_PICKER_PRIMITIVE = 9; + // // text + // const TEXT_PRIMITIVE = 10; + // // event_broadcast_menu + // const BROADCAST_PRIMITIVE = 11; + // // data_variable + // const VAR_PRIMITIVE = 12; + // // data_listcontents + // const LIST_PRIMITIVE = 13; + // if (blocks[(input as any).block]) { + // if (Object.keys(primitiveOpcodeInfoMap).includes(blocks[(input as any).block].opcode)) { + // const inputBlock = blocks[(input as any).block].fields; + // const inputType = Object.keys(blocks[(input as any).block].fields)[0]; + // var inputValue = inputBlock[inputType].value; + // if (inputType == "NUM") { + // inputValue = parseFloat(inputValue); + // } + // } else { + // variables[keyIndex] = input; + // inputValue = "0"; + // } + // args.push({id: keyIndex, value: inputValue}) + // } }) } return { inputs: args, variables: variables }; @@ -471,13 +710,13 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { return updatedDict; } - removeInput(block, removeBlock, blocks, argInfo, blockLib) { + removeInput(block, removeBlock, blocks, argInfo) { const inputs = block.inputs; const newInputs = {}; let objectBlocks = {}; for (const key of Object.keys(inputs)) { if (inputs[key] && inputs[key].block == removeBlock.id) { - const values = this.createInputBlock(blocks, argInfo[key].type, argInfo[key].defaultValue, block.id, blockLib); + const values = this.createInputBlock(blocks, argInfo[key].type, argInfo[key].defaultValue, block.id); objectBlocks = values.blocks; newInputs[key] = { name: String(key), diff --git a/extensions/src/common/types/framework/blocks.ts b/extensions/src/common/types/framework/blocks.ts index a4782f653..a619bfac0 100644 --- a/extensions/src/common/types/framework/blocks.ts +++ b/extensions/src/common/types/framework/blocks.ts @@ -277,7 +277,7 @@ type Operation = export type ScratchBlockType = typeof BlockType[keyof typeof BlockType]; -export type BlockType = "reporter" | "command"; // etc, use actual version +export type BlockType2 = "reporter" | "command"; // etc, use actual version export type ArgValue = any; export type ArgIdentifier = string | number; export type ArgEntry = { @@ -295,7 +295,7 @@ export type VersionedArgTransformer = (mechanism: VersionArgTransformMechanism) export type VersionedOptions = { transform?: VersionedArgTransformer; -previousType?: BlockType; +previousType?: BlockType2; previousName?: string; }; From 55696c8dd14974daa3c000b14197bbd165f832d0 Mon Sep 17 00:00:00 2001 From: ymayarajan3 Date: Thu, 27 Jun 2024 23:23:28 -0400 Subject: [PATCH 07/24] type block support --- .../mixins/base/scratchVersioning/index.ts | 57 +++++++++++++++---- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts index b4db74bc7..10073f063 100644 --- a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts @@ -270,10 +270,21 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { let parentIndex = parentBlock.opcode; parentIndex = parentIndex.replace(`${parentIndex.split("_")[0]}_`, ""); parentIndex = parentIndex.replace(regex, ""); - let argInfo = blocksInfo[parentIndex].arguments; - const values = this.removeInput(parentBlock, block, object.blocks, argInfo); - object.blocks[parentBlock.id] = values.block; - object.blocks = values.blocks; + for (let key of Object.keys(parentBlock.inputs)) { + let values = []; + for (const value of parentBlock.inputs[key]) { + if (value != blockId) { + if (value == 3) { + values.push(1) + } else { + values.push(value); + } + + } + + } + parentBlock.inputs[key] = values; + } block.parent = null; block.topLevel = true; } @@ -716,19 +727,41 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { let objectBlocks = {}; for (const key of Object.keys(inputs)) { if (inputs[key] && inputs[key].block == removeBlock.id) { - const values = this.createInputBlock(blocks, argInfo[key].type, argInfo[key].defaultValue, block.id); - objectBlocks = values.blocks; - newInputs[key] = { - name: String(key), - block: values.newId, - shadow: values.newId, - }; + const primitiveBlock = this.createInputBlock(blocks, argInfo[key].type, argInfo[key].defaultValue, block.id); + //objectBlocks = values.blocks; + let defaultVal = argInfo[key].defaultValue; + if (String(defaultVal) == "undefined") { + switch(argInfo[key].type) { + case "number": + case "angle": + case "color": { + defaultVal = 0; + break; + } + case "string": { + defaultVal = "string"; + break; + } + } + } + newInputs[key] = [ + 1, [ + primitiveOpcodeInfoMap[primitiveBlock.opcode][0], + defaultVal + ] + ] + // newInputs[key] = { + // name: String(key), + // block: values.newId, + // shadow: values.newId, + // }; } else { newInputs[key] = inputs[key]; } } + console.log(block.inputs); block.inputs = newInputs; - return { block: block, blocks: objectBlocks}; + return block; } From 77154668a96dc0af3db6814eded53e6afd663e7f Mon Sep 17 00:00:00 2001 From: ymayarajan3 Date: Fri, 28 Jun 2024 13:53:06 -0400 Subject: [PATCH 08/24] making variables work --- .../mixins/base/scratchVersioning/index.ts | 243 ++++-------------- 1 file changed, 53 insertions(+), 190 deletions(-) diff --git a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts index 10073f063..d1d4dba3d 100644 --- a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts @@ -104,16 +104,14 @@ const uid = function () { return id.join(''); }; -type VersionMap = Map; export default function (Ctor: BaseScratchExtensionConstuctor) { abstract class ExtensionWithConfigurableSupport extends Ctor { - alterJSON(projectJSON) { const targetObjects = projectJSON.targets .map((t, i) => Object.assign(t, { targetPaneOrder: i })) .sort((a, b) => a.layerOrder - b.layerOrder); - //const targetObjects = projectJSON.targets; + const newTargets = []; for (const object of targetObjects) { const newBlocks = {}; @@ -142,13 +140,11 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { acc[tempBlock.opcode] = tempBlock; return acc; }, {}); - const menuRegex = /menu_\d+/g; if (extensionID == this.getInfo().id) { const block = object.blocks[blockId]; let blockInfoIndex = block.opcode.replace(`${block.opcode.split("_")[0]}_`, ""); let oldIndex = blockInfoIndex; const nameMap = this.createNameMap(blocksInfo); - console.log(nameMap); if (nameMap[version] && nameMap[version][blockInfoIndex]) { blockInfoIndex = nameMap[version][blockInfoIndex]; } @@ -156,9 +152,9 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { const versions = this.getVersion(blockInfoIndex); if (versions && version < versions.length) { const blockArgs = this.removeImageEntries(blocksInfo[blockInfoIndex].arguments); - var { inputs, variables } = this.gatherInputs(object.blocks, block); - var fields = this.gatherFields(block, blockArgs); - var totalList = this.addInputsAndFields(inputs, fields); + let { inputs, variables } = this.gatherInputs(object.blocks, block); + let fields = this.gatherFields(block, blockArgs); + let totalList = this.addInputsAndFields(inputs, fields); const newInputs = {}; const newFields = {}; let changed = false; @@ -173,9 +169,10 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { arg: (identifier: ArgIdentifier) => map.get(identifier), args: () => Array.from(map.values()), } - const newEntries: ArgEntry[] = versions[i].transform(mechanism); - const { entries, mappings } = this.updateEntries(newEntries); - totalList = entries; + const entries: ArgEntry[] = versions[i].transform(mechanism); + console.log(entries); + const { newEntries, mappings } = this.updateEntries(entries); + totalList = newEntries; variables = this.updateDictionary(variables, mappings) } if (versions[i].previousType) { @@ -192,13 +189,13 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { } } } - + for (let i = 0; i < Object.keys(blockArgs).length; i++) { const argIndex = Object.keys(blockArgs)[i]; if (Object.keys(variables).includes(argIndex)) { newInputs[argIndex] = variables[argIndex]; } else if (blockArgs[argIndex].menu) { - var fieldValue = totalList[argIndex]; + let fieldValue = totalList[argIndex]; if (typeof fieldValue == "number") { fieldValue = String(fieldValue); } @@ -226,7 +223,6 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { const regex = /_v(\d+)/g; if (moveToSay && changed) { - console.log("new block"); const oldID = blockId; const next = block.next; block.id = uid(); @@ -272,27 +268,24 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { parentIndex = parentIndex.replace(regex, ""); for (let key of Object.keys(parentBlock.inputs)) { let values = []; + let index = 0; for (const value of parentBlock.inputs[key]) { if (value != blockId) { - if (value == 3) { - values.push(1) + if (index == 0) { + values.push(1); } else { values.push(value); } - } - + index = index + 1; } parentBlock.inputs[key] = values; } block.parent = null; block.topLevel = true; } - } } - //object.blocks[block.id] = block; - } if (!Object.keys(newBlocks).includes(blockId)) { newBlocks[blockId] = block; @@ -317,162 +310,19 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { } }; - - // alterProjectJSON(blocks, block, version, blockLib) { - // var addIds = []; - // const blocksInfo = this.getInfo().blocks.reduce((acc, tempBlock: any) => { - // acc[tempBlock.opcode] = tempBlock; - // return acc; - // }, {}); - // let blockInfoIndex = block.opcode.replace(`${block.opcode.split("_")[0]}_`, ""); - // let oldIndex = blockInfoIndex; - // const nameMap = this.createNameMap(blocksInfo); - // console.log(nameMap); - // if (nameMap[version] && nameMap[version][blockInfoIndex]) { - // blockInfoIndex = nameMap[version][blockInfoIndex]; - // } - // block.opcode = block.opcode.replace(oldIndex, blockInfoIndex); - // const versions = this.getVersion(blockInfoIndex); - // if (versions && version < versions.length) { - // const blockArgs = this.removeImageEntries(blocksInfo[blockInfoIndex].arguments); - // var { inputs, variables } = this.gatherInputs(blocks, block); - // var fields = this.gatherFields(block, blockArgs); - // var totalList = this.addInputsAndFields(inputs, fields); - // const newInputs = {}; - // const newFields = {}; - // let changed = false; - // let moveToSay = false; - // for (let i = version; i < versions.length; i++) { - // if (versions[i].transform) { - // const map = new Map(); - // for (let i = 0; i < totalList.length; i++) { - // map.set(totalList[i].id, totalList[i]); - // } - // const mechanism: VersionArgTransformMechanism = { - // arg: (identifier: ArgIdentifier) => map.get(identifier), - // args: () => Array.from(map.values()), - // } - // const newEntries: ArgEntry[] = versions[i].transform(mechanism); - // const { entries, mappings } = this.updateEntries(newEntries); - // totalList = entries; - // variables = this.updateDictionary(variables, mappings) - // } - // if (versions[i].previousType) { - // if (versions[i].previousType == "reporter" && blocksInfo[blockInfoIndex].blockType == "command") { // reporter to command - // changed = !changed; - // if (moveToSay) { - // moveToSay = false; - // } - // } else { // command to reporter - // changed = !changed; - // if (!moveToSay) { - // moveToSay = true; - // } - // } - // } - // } - - // for (let i = 0; i < Object.keys(blockArgs).length; i++) { - // const argIndex = Object.keys(blockArgs)[i]; - // if (Object.keys(variables).includes(argIndex)) { - // newInputs[argIndex] = variables[argIndex]; - // } else if (blockArgs[argIndex].menu) { - // var fieldValue = totalList[argIndex]; - // if (typeof fieldValue == "number") { - // fieldValue = String(fieldValue); - // } - // newFields[argIndex] = { - // name: String(argIndex), - // value: fieldValue, - // id: null - // } - // } else { - // const values = this.createInputBlock(blocks, blockArgs[argIndex].type, totalList[argIndex].value, block.id); - // const primitiveId = values.newId; - // blocks = values.blocks; - // newInputs[argIndex] = { - // name: String(argIndex), - // block: values.newId, - // shadow: values.newId - - // } - // } - - // } - - // // Re-assign fields and inputs - // block.inputs = newInputs; - // block.fields = newFields; - // blocks[block.id] = block; - // const regex = /_v(\d+)/g; - - // if (moveToSay && changed) { - // console.log("new block"); - // const oldID = block.id; - // const next = block.next; - // block.id = uid(); - // //blockJSON.topLevel = false; - // const newBlock = Object.create(null); - // newBlock.id = oldID; - // newBlock.parent = block.parent; - // block.parent = newBlock.id; - // newBlock.fields = {}; - // newBlock.inputs = { - // MESSAGE: { - // name: 'MESSAGE', - // block: block.id, - // shadow: block.id - // } - // } - // newBlock.next = next; - // block.next = null; - // newBlock.opcode = "looks_say"; - // newBlock.shadow = false; - // //newBlock.topLevel = true; - // for (const key of Object.keys(block.inputs)) { - // if (block.inputs[key].block) { - // let inputBlock = block.inputs[key].block; - // if (blocks[inputBlock]) { - // blocks[inputBlock].parent = block.id; - // block.inputs[key].shadow = block.inputs[key].block; - // } - - // } - // } - // blockLib.createBlock(newBlock); - // blockLib.createBlock(block); - // blocks[newBlock.id] = newBlock; - // blocks[block.id] = block; - // } else if (!moveToSay && changed) { - // if (blocks[block.parent]) { - // const parentBlock = blocks[block.parent]; - // if (parentBlock) { - // let parentIndex = parentBlock.opcode; - // parentIndex = parentIndex.replace(`${parentIndex.split("_")[0]}_`, ""); - // parentIndex = parentIndex.replace(regex, ""); - // let argInfo = blocksInfo[parentIndex].arguments; - // const values = this.removeInput(parentBlock, block, blocks, argInfo); - // blocks[parentBlock.id] = values.block; - // blocks = values.blocks; - // block.parent = null; - // block.topLevel = true; - // } - - // } - // } - // blocks[block.id] = block; - - // } - // return blocks; - // } - updateEntries(entries) { const mappings = {}; + console.log("entries"); + // let entries = Array.from(map.keys()); + let newEntries = []; + console.log(entries); + // console.log(map); for (let i = 0; i < entries.length; i++) { + // console.log(map.get(entries[i])); mappings[entries[i].id] = String(i); - entries[i].id = String(i); + newEntries.push({id: String(i), value: entries[i].value}); } - return { entries, mappings }; + return { newEntries, mappings }; } @@ -623,23 +473,35 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { Object.keys(blockJSON.inputs).forEach(input => { var keyIndex = input; input = blockJSON.inputs[input]; - console.log("INPUT"); - console.log(input); - const type = parseFloat(input[1][0]); - switch (type) { - case 6: - case 5: - case 8: - case 4: { - args.push({id: keyIndex, value: parseFloat(input[1][1])}) - break; - } - case 10: { - args.push({id: keyIndex, value: input[1][1]}) - break; + if (typeof input[1] == "string") { + console.log("VARIABLE"); + variables[keyIndex] = [ + 3, + input[1], + input[2] + ] + args.push({id: keyIndex, value: input[2][1]}); + } else { + const type = parseFloat(input[1][0]); + switch (type) { + case 6: + case 5: + case 8: + case 4: { + args.push({id: keyIndex, value: parseFloat(input[1][1])}); + break; + } + case 10: { + args.push({id: keyIndex, value: input[1][1]}) + break; + } + default: { + args.push({id: keyIndex, value: input[1][1]}); + break; + } } - } + // const MATH_NUM_PRIMITIVE = 4; // there's no reason these constants can't collide // // math_positive_number // const POSITIVE_NUM_PRIMITIVE = 5; // with the above, but removing duplication for clarity @@ -711,10 +573,12 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { updateDictionary(originalDict, keyMapping) { const updatedDict = {}; + console.log("mapping"); + console.log(keyMapping); for (const [oldKey, newKey] of Object.entries(keyMapping)) { if (originalDict.hasOwnProperty(oldKey)) { - const newEntry = { ...originalDict[oldKey] }; - newEntry.name = String(newKey); // Update the name field + const newEntry = originalDict[oldKey]; + //newEntry.name = String(newKey); // Update the name field updatedDict[newKey as any] = newEntry; } } @@ -759,7 +623,6 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { newInputs[key] = inputs[key]; } } - console.log(block.inputs); block.inputs = newInputs; return block; } From b4167dc83ebb10aa93fa9d5819e5fa3d11672f33 Mon Sep 17 00:00:00 2001 From: ymayarajan3 Date: Fri, 28 Jun 2024 13:55:46 -0400 Subject: [PATCH 09/24] a lil cleanup --- .../mixins/base/scratchVersioning/index.ts | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts index d1d4dba3d..dfdcc6bd5 100644 --- a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts @@ -170,7 +170,6 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { args: () => Array.from(map.values()), } const entries: ArgEntry[] = versions[i].transform(mechanism); - console.log(entries); const { newEntries, mappings } = this.updateEntries(entries); totalList = newEntries; variables = this.updateDictionary(variables, mappings) @@ -312,13 +311,8 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { updateEntries(entries) { const mappings = {}; - console.log("entries"); - // let entries = Array.from(map.keys()); let newEntries = []; - console.log(entries); - // console.log(map); for (let i = 0; i < entries.length; i++) { - // console.log(map.get(entries[i])); mappings[entries[i].id] = String(i); newEntries.push({id: String(i), value: entries[i].value}); } @@ -326,21 +320,6 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { } - // getNewIds(blockJSON) { - // console.log("here"); - // console.log(blockJSON); - // let ids = []; - // const inputs = blockJSON.inputs; - // console.log(inputs); - // for (const input of Object.keys(inputs)) { - // console.log(inputs[input]); - // if (inputs[input].block) { - // ids.push(inputs[input].block); - // } - // } - // return ids; - // } - createInputBlock(blocks, type, value, parentId) { value = String(value); const primitiveObj = Object.create(null); @@ -474,7 +453,6 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { var keyIndex = input; input = blockJSON.inputs[input]; if (typeof input[1] == "string") { - console.log("VARIABLE"); variables[keyIndex] = [ 3, input[1], @@ -573,12 +551,9 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { updateDictionary(originalDict, keyMapping) { const updatedDict = {}; - console.log("mapping"); - console.log(keyMapping); for (const [oldKey, newKey] of Object.entries(keyMapping)) { if (originalDict.hasOwnProperty(oldKey)) { const newEntry = originalDict[oldKey]; - //newEntry.name = String(newKey); // Update the name field updatedDict[newKey as any] = newEntry; } } From 9df1227805a4de78582608107cca63945e6fc35b Mon Sep 17 00:00:00 2001 From: ymayarajan3 Date: Fri, 28 Jun 2024 15:16:37 -0400 Subject: [PATCH 10/24] adding documentation --- .../mixins/base/scratchVersioning/index.ts | 262 ++++++++---------- 1 file changed, 111 insertions(+), 151 deletions(-) diff --git a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts index dfdcc6bd5..ee47bb1b8 100644 --- a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts @@ -107,7 +107,14 @@ const uid = function () { export default function (Ctor: BaseScratchExtensionConstuctor) { abstract class ExtensionWithConfigurableSupport extends Ctor { - alterJSON(projectJSON) { + /** + * A function that modifies a project JSON based on any updated + * versioning implementations + * + * @param {object} projectJSON The project JSON to be modified + * @return {object} An updated project JSON compatible with the current version of the extension + */ + alterJSON(projectJSON: any) { const targetObjects = projectJSON.targets .map((t, i) => Object.assign(t, { targetPaneOrder: i })) .sort((a, b) => a.layerOrder - b.layerOrder); @@ -152,9 +159,9 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { const versions = this.getVersion(blockInfoIndex); if (versions && version < versions.length) { const blockArgs = this.removeImageEntries(blocksInfo[blockInfoIndex].arguments); - let { inputs, variables } = this.gatherInputs(object.blocks, block); + let { inputs, variables } = this.gatherInputs(block); let fields = this.gatherFields(block, blockArgs); - let totalList = this.addInputsAndFields(inputs, fields); + let totalList = inputs.concat(fields); const newInputs = {}; const newFields = {}; let changed = false; @@ -205,10 +212,10 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { } } else { - const primitiveBlock = this.createInputBlock(object.blocks, blockArgs[argIndex].type, totalList[argIndex].value, block.id); + const typeNum = this.getType(blockArgs[argIndex].type); newInputs[argIndex] = [ 1, [ - primitiveOpcodeInfoMap[primitiveBlock.opcode][0], + typeNum, String(totalList[argIndex].value) ] ] @@ -299,7 +306,13 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { return projectJSON; } - getExtensionIdForOpcode(opcode) { + /** + * Helper function to get the extension ID from a block's opcode + * + * @param {string} opcode The block's opcode + * @return {string} The extension ID + */ + getExtensionIdForOpcode(opcode: string): string { // Allowed ID characters are those matching the regular expression [\w-]: A-Z, a-z, 0-9, and hyphen ("-"). const index = opcode.indexOf('_'); const forbiddenSymbols = /[^\w-]/g; @@ -309,91 +322,35 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { } }; - updateEntries(entries) { + /** + * Helper function used to create a position mapping from one version to another as well as + * assign each ArgEntry object a new ID + * + * @param {ArgEntry[]} entries The updated entries to create the mapping from + * @return {ArgEntry[]} returns.newEntries - The ArgEntry objects with updated IDs + * @return {object} returns.mappings - A dictionary with the position mappings + */ + updateEntries(entries: ArgEntry[]) { const mappings = {}; let newEntries = []; for (let i = 0; i < entries.length; i++) { - mappings[entries[i].id] = String(i); + if (entries[i].id) { + mappings[entries[i].id] = String(i); + } newEntries.push({id: String(i), value: entries[i].value}); } return { newEntries, mappings }; } - createInputBlock(blocks, type, value, parentId) { - value = String(value); - const primitiveObj = Object.create(null); - const newId = uid(); - primitiveObj.id = newId; - primitiveObj.next = null; - primitiveObj.parent = parentId; - primitiveObj.shadow = true; - primitiveObj.inputs = Object.create(null); - // need a reference to parent id - switch (type) { - case "number": { - if (value == "undefined") { - value = 0; - } - primitiveObj.opcode = 'math_number'; - primitiveObj.fields = { - NUM: { - name: 'NUM', - value: value - } - }; - primitiveObj.topLevel = false; - break; - } - case "angle": { - if (value == "undefined") { - value = 0; - } - primitiveObj.opcode = 'math_angle'; - primitiveObj.fields = { - NUM: { - name: 'NUM', - value: value - } - }; - primitiveObj.topLevel = false; - break; - } - case "color": { - if (value == "undefined") { - value = 0; - } - primitiveObj.opcode = 'colour_picker'; - primitiveObj.fields = { - COLOUR: { - name: 'COLOUR', - value: value - } - }; - primitiveObj.topLevel = false; - break; - } - case "string": { - primitiveObj.opcode = 'text'; - primitiveObj.fields = { - TEXT: { - name: 'TEXT', - value: value - } - }; - primitiveObj.topLevel = false; - break; - } - default: { - //log.error(`Found unknown primitive type during deserialization: ${JSON.stringify(inputDescOrId)}`); - return null; - } - } - //blocks[newId] = primitiveObj; - //blockLib.createBlock(primitiveObj); - return primitiveObj; - }; - + /** + * This function creates a dictionary with the opcode associated with each block + * at each version, so that if an opcode changes during a version, we'll be able + * to find the correct block. + * + * @param {object} blockInfo A dictionary with the information of each block from the extension + * @return {object} The dictionary with each block's opcode at each version + */ createNameMap(blocksInfo) { const versionMap = new Map(); for (const opcode of Object.keys(blocksInfo)) { @@ -421,6 +378,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { /** * Remove image entries from the passed-in arguments for each block from the extension + * * @param {object} dict The arguments of the block * @return {object} The arguments of the block with the static images removed */ @@ -437,15 +395,13 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { } /** - * Gather the primitive values from the 'inputs' property of a block as well as the block's variables + * Gather the values from the 'inputs' property of a block as well as the block's variables * - * @param {object} blocks The blocks related to the Scratch object * @param {object} blockJSON The block to gather the inputs from - * @return {object} return.inputs - A dictionary of the inputs, with each value a primitive object + * @return {ArgEntry[]} return.inputs - An array of ArgEntry objects with each value representing an input * @return {object} return.variables - A dictionary with all th block's variables as values and their positions as keys */ - gatherInputs(blocks, blockJSON) { - var inputs = {}; + gatherInputs(blockJSON: any): any { var variables = {}; const args: ArgEntry[] = []; if (blockJSON.inputs && Object.keys(blockJSON.inputs).length > 0) { @@ -462,62 +418,35 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { } else { const type = parseFloat(input[1][0]); switch (type) { - case 6: - case 5: - case 8: - case 4: { + case 6: // WHOLE_NUM_PRIMITIVE + case 5: // POSITIVE_NUM_PRIMITIVE + case 7: // INTEGER_NUM_PRIMITIVE + case 8: // ANGLE_NUM_PRIMITIVE + case 9: // COLOR_PICKER_PRIMITIVE + case 4: // MATH_NUM_PRIMITIVE + { args.push({id: keyIndex, value: parseFloat(input[1][1])}); break; } - case 10: { + case 10: // TEXT_PRIMITIVE + default: // BROADCAST_PRIMITIVE, VAR_PRIMITIVE, LIST_PRIMITIVE + { args.push({id: keyIndex, value: input[1][1]}) break; } - default: { - args.push({id: keyIndex, value: input[1][1]}); - break; - } } } - -// const MATH_NUM_PRIMITIVE = 4; // there's no reason these constants can't collide - // // math_positive_number - // const POSITIVE_NUM_PRIMITIVE = 5; // with the above, but removing duplication for clarity - // // math_whole_number - // const WHOLE_NUM_PRIMITIVE = 6; - // // math_integer - // const INTEGER_NUM_PRIMITIVE = 7; - // // math_angle - // const ANGLE_NUM_PRIMITIVE = 8; - // // colour_picker - // const COLOR_PICKER_PRIMITIVE = 9; - // // text - // const TEXT_PRIMITIVE = 10; - // // event_broadcast_menu - // const BROADCAST_PRIMITIVE = 11; - // // data_variable - // const VAR_PRIMITIVE = 12; - // // data_listcontents - // const LIST_PRIMITIVE = 13; - // if (blocks[(input as any).block]) { - // if (Object.keys(primitiveOpcodeInfoMap).includes(blocks[(input as any).block].opcode)) { - // const inputBlock = blocks[(input as any).block].fields; - // const inputType = Object.keys(blocks[(input as any).block].fields)[0]; - // var inputValue = inputBlock[inputType].value; - // if (inputType == "NUM") { - // inputValue = parseFloat(inputValue); - // } - // } else { - // variables[keyIndex] = input; - // inputValue = "0"; - // } - // args.push({id: keyIndex, value: inputValue}) - // } }) } return { inputs: args, variables: variables }; } + /** + * Gather the values from the 'fields' property of a block + * + * @param {object} blockJSON The block to gather the inputs from + * @return {ArgEntry[]} An array of ArgEntry objects representing each field + */ gatherFields(blockJSON, argList) { const args: ArgEntry[] = []; var fields = {}; @@ -537,18 +466,14 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { } /** - * A function that combines the primitive values from the block's inputs and fields - * so that it can be processed by the version functions + * If the positions of the arguments change between version transformations, we + * use this function to update the variables dictionary to contain each variable's + * new position * - * @param {object} inputs A dictionary with the primitive values of the block's inputs - * @param {object} fields A dictionary with the primitive values of the block's fields - * @param {object} argList The argument dictionary for the associated block - * @return {Array} An array with the combined primitive values from the block's inputs and fields - */ - addInputsAndFields(inputs, fields) { - return inputs.concat(fields); - } - + * @param {object} originalDict The dictionary with the old variable positions + * @param {object} keyMapping The position transformations corresponding to a version transformation + * @return {object} A dictionary with the new variable positions + */ updateDictionary(originalDict, keyMapping) { const updatedDict = {}; for (const [oldKey, newKey] of Object.entries(keyMapping)) { @@ -560,14 +485,54 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { return updatedDict; } - removeInput(block, removeBlock, blocks, argInfo) { + /** + * Taking a type as provided by the argument info from the extension and converting it + * to an integer (as would be represented in the JSON) + * + * @param {string} type The type of the value + * @return {number} The enum value of the type + */ + getType(type: string): number { + let opcode; + switch (type) { + case "number": { + opcode = 'math_number'; + break; + } + case "angle": { + opcode = 'math_angle'; + break; + } + case "color": { + opcode = 'colour_picker'; + break; + } + case "string": + default: + { + opcode = 'text'; + break; + } + } + return primitiveOpcodeInfoMap[opcode][0]; + } + + /** + * If a block changes from a reporter to a command, we use this function + * to remove the reporter version from its parent in the JSON + * + * @param {object} block The parent block we'll use to remove the input + * @param {object} removeBlock The block to remove + * @param {object} argInfo The type info for the block's arguments so we can + * correctly set the default value + * @return {number} The parent block with the input removed + */ + removeInput(block, removeBlock, argInfo) { const inputs = block.inputs; const newInputs = {}; - let objectBlocks = {}; for (const key of Object.keys(inputs)) { if (inputs[key] && inputs[key].block == removeBlock.id) { - const primitiveBlock = this.createInputBlock(blocks, argInfo[key].type, argInfo[key].defaultValue, block.id); - //objectBlocks = values.blocks; + const typeNum = this.getType(argInfo[key].type); let defaultVal = argInfo[key].defaultValue; if (String(defaultVal) == "undefined") { switch(argInfo[key].type) { @@ -585,15 +550,10 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { } newInputs[key] = [ 1, [ - primitiveOpcodeInfoMap[primitiveBlock.opcode][0], + typeNum, defaultVal ] ] - // newInputs[key] = { - // name: String(key), - // block: values.newId, - // shadow: values.newId, - // }; } else { newInputs[key] = inputs[key]; } From 7b31271b5d3be569617eb9a268d21b641dc72295 Mon Sep 17 00:00:00 2001 From: ymayarajan3 Date: Fri, 28 Jun 2024 17:06:23 -0400 Subject: [PATCH 11/24] more documentation --- .../mixins/base/scratchVersioning/index.ts | 132 ++++++++++++------ 1 file changed, 91 insertions(+), 41 deletions(-) diff --git a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts index ee47bb1b8..737660b0c 100644 --- a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts @@ -115,6 +115,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { * @return {object} An updated project JSON compatible with the current version of the extension */ alterJSON(projectJSON: any) { + // Collect the targets const targetObjects = projectJSON.targets .map((t, i) => Object.assign(t, { targetPaneOrder: i })) .sort((a, b) => a.layerOrder - b.layerOrder); @@ -147,6 +148,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { acc[tempBlock.opcode] = tempBlock; return acc; }, {}); + // If the block is under the current extension if (extensionID == this.getInfo().id) { const block = object.blocks[blockId]; let blockInfoIndex = block.opcode.replace(`${block.opcode.split("_")[0]}_`, ""); @@ -155,10 +157,15 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { if (nameMap[version] && nameMap[version][blockInfoIndex]) { blockInfoIndex = nameMap[version][blockInfoIndex]; } + // Update the opcode to be the current version name block.opcode = block.opcode.replace(oldIndex, blockInfoIndex); const versions = this.getVersion(blockInfoIndex); + + // If we need to update the JSON to be compatible with the current version if (versions && version < versions.length) { + // Remove the image entries from the arguments const blockArgs = this.removeImageEntries(blocksInfo[blockInfoIndex].arguments); + // Gather values let { inputs, variables } = this.gatherInputs(block); let fields = this.gatherFields(block, blockArgs); let totalList = inputs.concat(fields); @@ -166,8 +173,11 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { const newFields = {}; let changed = false; let moveToSay = false; + + // Apply each version modification as needed for (let i = version; i < versions.length; i++) { if (versions[i].transform) { + // Create the map to be used in the mechanism from ArgEntry objects const map = new Map(); for (let i = 0; i < totalList.length; i++) { map.set(totalList[i].id, totalList[i]); @@ -176,12 +186,15 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { arg: (identifier: ArgIdentifier) => map.get(identifier), args: () => Array.from(map.values()), } + // Complete the transformation const entries: ArgEntry[] = versions[i].transform(mechanism); + // Update the ArgEntry objects' IDs and get position mappings const { newEntries, mappings } = this.updateEntries(entries); totalList = newEntries; + // Update variable positions variables = this.updateDictionary(variables, mappings) } - if (versions[i].previousType) { + if (versions[i].previousType) { if (versions[i].previousType == "reporter" && blocksInfo[blockInfoIndex].blockType == "command") { // reporter to command changed = !changed; if (moveToSay) { @@ -196,11 +209,15 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { } } + // Re-create the project JSON inputs/fields with the new values for (let i = 0; i < Object.keys(blockArgs).length; i++) { const argIndex = Object.keys(blockArgs)[i]; + // If there's a variable in the argIndex position if (Object.keys(variables).includes(argIndex)) { newInputs[argIndex] = variables[argIndex]; - } else if (blockArgs[argIndex].menu) { + } + // If we need to place the value in a field + else if (blockArgs[argIndex].menu) { let fieldValue = totalList[argIndex]; if (typeof fieldValue == "number") { fieldValue = String(fieldValue); @@ -210,8 +227,9 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { value: fieldValue, id: null } - - } else { + } + // If we need to place the value in an input + else { const typeNum = this.getType(blockArgs[argIndex].type); newInputs[argIndex] = [ 1, [ @@ -220,24 +238,28 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { ] ] } - } // Re-assign fields and inputs block.inputs = newInputs; block.fields = newFields; + const regex = /_v(\d+)/g; - if (moveToSay && changed) { + // If we need to move the information to a 'say' block, since + // we need to keep it connected to the previous/next blocks + if (moveToSay && changed) { // command to reporter const oldID = blockId; const next = block.next; + // Re-assign the ID of the current block block.id = uid(); - //blockJSON.topLevel = false; + // Create the new block const newBlock = Object.create(null); newBlock.id = oldID; newBlock.parent = block.parent; block.parent = newBlock.id; newBlock.fields = {}; + // Input should be the reporter block variable newBlock.inputs = { MESSAGE: [ 3, @@ -252,7 +274,9 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { block.next = null; newBlock.opcode = "looks_say"; newBlock.shadow = false; - //newBlock.topLevel = true; + + // Since we changed the block ID, we need to update the + // block's input blocks to match for (const key of Object.keys(block.inputs)) { if (block.inputs[key].block) { let inputBlock = block.inputs[key].block; @@ -263,45 +287,50 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { } } + // Set the blocks in the JSON newBlocks[block.id] = block; newBlocks[newBlock.id] = newBlock; - } else if (!moveToSay && changed) { + } + // If we need to create a command from a reporter + else if (!moveToSay && changed) { + // If the previous reporter block has a parent if (object.blocks[block.parent]) { const parentBlock = object.blocks[block.parent]; - if (parentBlock) { - let parentIndex = parentBlock.opcode; - parentIndex = parentIndex.replace(`${parentIndex.split("_")[0]}_`, ""); - parentIndex = parentIndex.replace(regex, ""); - for (let key of Object.keys(parentBlock.inputs)) { - let values = []; - let index = 0; - for (const value of parentBlock.inputs[key]) { - if (value != blockId) { - if (index == 0) { - values.push(1); - } else { - values.push(value); - } + // Remove the reporter variable from the parent block's inputs + for (let key of Object.keys(parentBlock.inputs)) { + let values = []; + let index = 0; + for (const value of parentBlock.inputs[key]) { + if (value != blockId) { + if (index == 0) { + // We'll also need to set the shadow to 1 + // since we removed the variable + values.push(1); + } else { + values.push(value); } - index = index + 1; } - parentBlock.inputs[key] = values; + index = index + 1; } - block.parent = null; - block.topLevel = true; - } + parentBlock.inputs[key] = values; + } + // Update the current block to be a command block + block.parent = null; + block.topLevel = true; } } } + // Set the blocks dictionary if (!Object.keys(newBlocks).includes(blockId)) { newBlocks[blockId] = block; } - } } + // Update the target with the new blocks object.blocks = newBlocks; newTargets.push(object); } + // Update the project JSON with the new target projectJSON.targets = newTargets; return projectJSON; } @@ -333,10 +362,14 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { updateEntries(entries: ArgEntry[]) { const mappings = {}; let newEntries = []; + // Loop through the arg entries for (let i = 0; i < entries.length; i++) { + // If the value hasn't just been added if (entries[i].id) { + // Set the positional dictionary mappings[entries[i].id] = String(i); } + // Update the new ArgEntry array with the new ID newEntries.push({id: String(i), value: entries[i].value}); } return { newEntries, mappings }; @@ -351,21 +384,27 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { * @param {object} blockInfo A dictionary with the information of each block from the extension * @return {object} The dictionary with each block's opcode at each version */ - createNameMap(blocksInfo) { + createNameMap(blocksInfo: any) { const versionMap = new Map(); + // Loop through every block in the extension for (const opcode of Object.keys(blocksInfo)) { + // Get version information for each extension const versions = this.getVersion(opcode); + // If there is version information if (versions && versions.length > 0) { let tempName = opcode; - for (let index = versions.length - 1; index >= 0; index--) { // loop through each version entry + // Loop through the versions from most current to least current + for (let index = versions.length - 1; index >= 0; index--) { if (!versionMap.has(index)) { versionMap[index] = {}; } + // Collect the name for the version, if it's not the current opcode const version = versions[index]; if (typeof version == "object" && version.previousName) { // check if the version entry has a name const oldName = version.previousName; tempName = oldName; } + // Set the map entry for the version and the name at that version if (tempName != opcode) { versionMap[index][tempName] = opcode; } @@ -384,13 +423,11 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { */ removeImageEntries(dict: any) { const filteredDict = {}; - for (const [key, value] of Object.entries(dict)) { if ((value as any).type !== 'image') { filteredDict[key] = value; } } - return filteredDict; } @@ -404,11 +441,14 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { gatherInputs(blockJSON: any): any { var variables = {}; const args: ArgEntry[] = []; + // Loop through the block's inputs if (blockJSON.inputs && Object.keys(blockJSON.inputs).length > 0) { Object.keys(blockJSON.inputs).forEach(input => { var keyIndex = input; input = blockJSON.inputs[input]; + // If there is a variable in the input if (typeof input[1] == "string") { + // Set the variables dictionary accordingly variables[keyIndex] = [ 3, input[1], @@ -416,6 +456,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { ] args.push({id: keyIndex, value: input[2][1]}); } else { + // If the input is a value, push that value according to type const type = parseFloat(input[1][0]); switch (type) { case 6: // WHOLE_NUM_PRIMITIVE @@ -447,15 +488,17 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { * @param {object} blockJSON The block to gather the inputs from * @return {ArgEntry[]} An array of ArgEntry objects representing each field */ - gatherFields(blockJSON, argList) { + gatherFields(blockJSON: any, argList: any) { const args: ArgEntry[] = []; - var fields = {}; + // Loop through each field if (blockJSON.fields && Object.keys(blockJSON.fields).length > 0) { Object.keys(blockJSON.fields).forEach(field => { const keyIndex = field; field = blockJSON.fields[field]; + // Collect the field's value var value = (field as any).value; var argType = argList[(field as any).name].type; + // Convert the value to its correct type if (argType == "number" || argType == "angle") { value = parseFloat(value); } @@ -474,11 +517,13 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { * @param {object} keyMapping The position transformations corresponding to a version transformation * @return {object} A dictionary with the new variable positions */ - updateDictionary(originalDict, keyMapping) { + updateDictionary(originalDict: any, keyMapping: any) { const updatedDict = {}; for (const [oldKey, newKey] of Object.entries(keyMapping)) { + // If the old variable exists at that position... if (originalDict.hasOwnProperty(oldKey)) { const newEntry = originalDict[oldKey]; + // ... update the new dictionary at the new position updatedDict[newKey as any] = newEntry; } } @@ -493,7 +538,8 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { * @return {number} The enum value of the type */ getType(type: string): number { - let opcode; + let opcode: string; + // Collect the opcode for each type switch (type) { case "number": { opcode = 'math_number'; @@ -514,6 +560,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { break; } } + // Map the opcode to its enum type return primitiveOpcodeInfoMap[opcode][0]; } @@ -527,11 +574,14 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { * correctly set the default value * @return {number} The parent block with the input removed */ - removeInput(block, removeBlock, argInfo) { + removeInput(block: any, removeBlock: any, argInfo: any) { const inputs = block.inputs; const newInputs = {}; + // Loop through block inputs for (const key of Object.keys(inputs)) { - if (inputs[key] && inputs[key].block == removeBlock.id) { + // If the input contains the block we need to remove + if (inputs[key] && inputs[key].includes(removeBlock.id)) { + // Get the default value for the current input position const typeNum = this.getType(argInfo[key].type); let defaultVal = argInfo[key].defaultValue; if (String(defaultVal) == "undefined") { @@ -548,12 +598,14 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { } } } + // Set the input value to be the default instead of the variable newInputs[key] = [ 1, [ typeNum, defaultVal ] ] + // Otherwise, leave the input as is } else { newInputs[key] = inputs[key]; } @@ -561,8 +613,6 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { block.inputs = newInputs; return block; } - - } return ExtensionWithConfigurableSupport; From f52f030a320e1ee958326dd5b179e7262bf0d5dd Mon Sep 17 00:00:00 2001 From: ymayarajan3 Date: Fri, 28 Jun 2024 17:16:31 -0400 Subject: [PATCH 12/24] remove vars --- .../extension/mixins/base/scratchVersioning/index.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts index 737660b0c..5d10df96d 100644 --- a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts @@ -133,6 +133,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { const regex = /_v(\d+)/g; const matches = blockOpcode.match(regex); // Get all matches + // Collect the version number if (matches) { const lastMatch = matches[matches.length - 1]; const versionMatch = lastMatch.match(/_v(\d+)/); @@ -436,15 +437,15 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { * * @param {object} blockJSON The block to gather the inputs from * @return {ArgEntry[]} return.inputs - An array of ArgEntry objects with each value representing an input - * @return {object} return.variables - A dictionary with all th block's variables as values and their positions as keys + * @return {object} return.variables - A dictionary with all the block's variables as values and their positions as keys */ gatherInputs(blockJSON: any): any { - var variables = {}; + const variables = {}; const args: ArgEntry[] = []; // Loop through the block's inputs if (blockJSON.inputs && Object.keys(blockJSON.inputs).length > 0) { Object.keys(blockJSON.inputs).forEach(input => { - var keyIndex = input; + const keyIndex = input; input = blockJSON.inputs[input]; // If there is a variable in the input if (typeof input[1] == "string") { @@ -496,8 +497,8 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { const keyIndex = field; field = blockJSON.fields[field]; // Collect the field's value - var value = (field as any).value; - var argType = argList[(field as any).name].type; + let value = (field as any).value; + let argType = argList[(field as any).name].type; // Convert the value to its correct type if (argType == "number" || argType == "angle") { value = parseFloat(value); From ce4dce4d7f45f202228c0f613c0e4bc7531a0c78 Mon Sep 17 00:00:00 2001 From: ymayarajan3 Date: Fri, 28 Jun 2024 17:18:30 -0400 Subject: [PATCH 13/24] update types --- .../mixins/base/scratchVersioning/index.ts | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts index 5d10df96d..999b5966c 100644 --- a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts @@ -1,4 +1,5 @@ import { BaseScratchExtensionConstuctor } from ".."; +import { ArgEntry, VersionArgTransformMechanism, ArgIdentifier } from "$common/types"; /** * Mixin the ability for extensions to have their blocks 'versioned', @@ -8,29 +9,6 @@ import { BaseScratchExtensionConstuctor } from ".."; * @see https://www.typescriptlang.org/docs/handbook/mixins.html */ -type BlockType = "reporter" | "command"; // etc, use actual version -type ArgValue = any; -type ArgIdentifier = string | number; -type ArgEntry = { - /** If no id is provided, we can assume that the associated value does not correspond to any previously serialized argument */ - readonly id?: ArgIdentifier, - value: ArgValue -} - - - -type VersionArgTransformMechanism = { - arg: (identifier: ArgIdentifier) => ArgEntry, - args: () => ArgEntry[] -} - -type VersionedArgTransformer = (mechanism: VersionArgTransformMechanism) => ArgEntry[]; - -type VersionedOptions = { - transform?: VersionedArgTransformer; - previousType?: BlockType; - previousName?: string; -}; const CORE_EXTENSIONS = [ 'argument', From 3f7123da422e4f670c8e34bbe70d2bd73f20088a Mon Sep 17 00:00:00 2001 From: ymayarajan3 Date: Fri, 28 Jun 2024 18:12:11 -0400 Subject: [PATCH 14/24] adding on save versioning --- .../mixins/base/scratchVersioning/index.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts index 999b5966c..c9fde1970 100644 --- a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts @@ -85,6 +85,33 @@ const uid = function () { export default function (Ctor: BaseScratchExtensionConstuctor) { abstract class ExtensionWithConfigurableSupport extends Ctor { + /** + * A function that appends the version number to each opcode on project save + * + * @param {Array} objTargets The obj.targets array from the Scratch project + * @return {Array} An updated object.targets array with the new opcodes + */ + alterOpcodes(objTargets: any) { + const newTargets = []; + // Loop through the object targets + for (const object of objTargets) { + for (const blockId in object.blocks) { + const block = object.blocks[blockId]; + // Get the opcode from the block + let blockInfoIndex = block.opcode.replace(`${block.opcode.split("_")[0]}_`, ""); + // Add the version number to the opcode + const versions = this.getVersion(blockInfoIndex); + if (versions && versions.length > 0) { + object.blocks[blockId].opcode = `${object.blocks[blockId].opcode}_v${versions.length}`; + } else { + object.blocks[blockId].opcode = `${object.blocks[blockId].opcode}_v0`; + } + } + newTargets.push(object); + } + return newTargets; + } + /** * A function that modifies a project JSON based on any updated * versioning implementations From f5eb5ac52cf7ae8889b460abd902dd6b2121c288 Mon Sep 17 00:00:00 2001 From: ymayarajan3 Date: Fri, 28 Jun 2024 19:31:10 -0400 Subject: [PATCH 15/24] adding hats --- .../extension/decorators/taggedTemplate.ts | 1 + .../mixins/base/scratchVersioning/index.ts | 174 +++++++++--------- .../src/common/types/framework/blocks.ts | 2 +- 3 files changed, 93 insertions(+), 84 deletions(-) diff --git a/extensions/src/common/extension/decorators/taggedTemplate.ts b/extensions/src/common/extension/decorators/taggedTemplate.ts index 7d896a44f..96b9d4397 100644 --- a/extensions/src/common/extension/decorators/taggedTemplate.ts +++ b/extensions/src/common/extension/decorators/taggedTemplate.ts @@ -82,5 +82,6 @@ interface TemplateEngine { export const scratch = { reporter: makeDecorator("reporter"), command: makeDecorator("command"), + hat: makeDecorator("hat"), button: makeDecorator("button"), } \ No newline at end of file diff --git a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts index c9fde1970..4cbf1aa29 100644 --- a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts @@ -129,7 +129,6 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { for (const object of targetObjects) { const newBlocks = {}; for (const blockId in object.blocks) { - let blockJSON = object.blocks[blockId]; let version = 0; const blockOpcode = blockJSON.opcode; @@ -154,9 +153,10 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { acc[tempBlock.opcode] = tempBlock; return acc; }, {}); + + const block = object.blocks[blockId]; // If the block is under the current extension - if (extensionID == this.getInfo().id) { - const block = object.blocks[blockId]; + if (extensionID == this.getInfo().id && !block.opcode.includes("_menu_")) { let blockInfoIndex = block.opcode.replace(`${block.opcode.split("_")[0]}_`, ""); let oldIndex = blockInfoIndex; const nameMap = this.createNameMap(blocksInfo); @@ -167,6 +167,9 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { block.opcode = block.opcode.replace(oldIndex, blockInfoIndex); const versions = this.getVersion(blockInfoIndex); + let originalType = blocksInfo[blockInfoIndex].blockType; + let first = true; + // If we need to update the JSON to be compatible with the current version if (versions && version < versions.length) { // Remove the image entries from the arguments @@ -181,6 +184,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { let moveToSay = false; // Apply each version modification as needed + for (let i = version; i < versions.length; i++) { if (versions[i].transform) { // Create the map to be used in the mechanism from ArgEntry objects @@ -201,16 +205,9 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { variables = this.updateDictionary(variables, mappings) } if (versions[i].previousType) { - if (versions[i].previousType == "reporter" && blocksInfo[blockInfoIndex].blockType == "command") { // reporter to command - changed = !changed; - if (moveToSay) { - moveToSay = false; - } - } else { // command to reporter - changed = !changed; - if (!moveToSay) { - moveToSay = true; - } + if (first) { + originalType = versions[i].previousType; + first = false; } } } @@ -249,88 +246,99 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { // Re-assign fields and inputs block.inputs = newInputs; block.fields = newFields; - - const regex = /_v(\d+)/g; // If we need to move the information to a 'say' block, since // we need to keep it connected to the previous/next blocks - if (moveToSay && changed) { // command to reporter - const oldID = blockId; - const next = block.next; - // Re-assign the ID of the current block - block.id = uid(); - // Create the new block - const newBlock = Object.create(null); - newBlock.id = oldID; - newBlock.parent = block.parent; - block.parent = newBlock.id; - newBlock.fields = {}; - // Input should be the reporter block variable - newBlock.inputs = { - MESSAGE: [ - 3, - block.id, - [ - 10, - "Hello" + if (originalType != blocksInfo[blockInfoIndex].blockType) { + if ((originalType == "command" || originalType == "hat") && blocksInfo[blockInfoIndex].blockType == "reporter") { // square to circle + const oldID = blockId; + const next = block.next; + // Re-assign the ID of the current block + block.id = uid(); + // Create the new block + const newBlock = Object.create(null); + newBlock.id = oldID; + newBlock.parent = block.parent; + block.parent = newBlock.id; + newBlock.fields = {}; + // Input should be the reporter block variable + newBlock.inputs = { + MESSAGE: [ + 3, + block.id, + [ + 10, + "Hello" + ] ] - ] - } - newBlock.next = next; - block.next = null; - newBlock.opcode = "looks_say"; - newBlock.shadow = false; - - // Since we changed the block ID, we need to update the - // block's input blocks to match - for (const key of Object.keys(block.inputs)) { - if (block.inputs[key].block) { - let inputBlock = block.inputs[key].block; - if (object.blocks[inputBlock]) { - object.blocks[inputBlock].parent = block.id; - block.inputs[key].shadow = block.inputs[key].block; + } + newBlock.next = next; + block.next = null; + newBlock.opcode = "looks_say"; + newBlock.shadow = false; + + // Since we changed the block ID, we need to update the + // block's input blocks to match + for (const key of Object.keys(block.inputs)) { + if (block.inputs[key].block) { + let inputBlock = block.inputs[key].block; + if (object.blocks[inputBlock]) { + object.blocks[inputBlock].parent = block.id; + block.inputs[key].shadow = block.inputs[key].block; + } + } - } - } - // Set the blocks in the JSON - newBlocks[block.id] = block; - newBlocks[newBlock.id] = newBlock; - } - // If we need to create a command from a reporter - else if (!moveToSay && changed) { - // If the previous reporter block has a parent - if (object.blocks[block.parent]) { - const parentBlock = object.blocks[block.parent]; - // Remove the reporter variable from the parent block's inputs - for (let key of Object.keys(parentBlock.inputs)) { - let values = []; - let index = 0; - for (const value of parentBlock.inputs[key]) { - if (value != blockId) { - if (index == 0) { - // We'll also need to set the shadow to 1 - // since we removed the variable - values.push(1); - } else { - values.push(value); + // Set the blocks in the JSON + newBlocks[block.id] = block; + newBlocks[newBlock.id] = newBlock; + } + // If we need to create a command from a reporter + else if (originalType == "reporter" && (blocksInfo[blockInfoIndex].blockType == "command" || blocksInfo[blockInfoIndex].blockType == "hat")) { // reporter to command) { // circle to square + // If the previous reporter block has a parent + if (object.blocks[block.parent]) { + const parentBlock = object.blocks[block.parent]; + // Remove the reporter variable from the parent block's inputs + for (let key of Object.keys(parentBlock.inputs)) { + let values = []; + let index = 0; + for (const value of parentBlock.inputs[key]) { + if (value != blockId) { + if (index == 0) { + // We'll also need to set the shadow to 1 + // since we removed the variable + values.push(1); + } else { + values.push(value); + } } + index = index + 1; } - index = index + 1; + parentBlock.inputs[key] = values; } - parentBlock.inputs[key] = values; + // Update the current block to be a command block + block.parent = null; + block.topLevel = true; + } + } else if ((originalType == "command") && (blocksInfo[blockInfoIndex].blockType == "hat")) { + if (object.blocks[block.parent]) { + const oldId = block.parent; + const parentBlock = object.blocks[block.parent]; + parentBlock.next = null; + block.parent = null; + block.topLevel = true; + newBlocks[blockId] = block; + newBlocks[oldId] = parentBlock; } - // Update the current block to be a command block - block.parent = null; - block.topLevel = true; - } - } - } - // Set the blocks dictionary - if (!Object.keys(newBlocks).includes(blockId)) { - newBlocks[blockId] = block; + } + } } + } + // Set the blocks dictionary + if (!Object.keys(newBlocks).includes(blockId)) { + newBlocks[blockId] = block; + } } // Update the target with the new blocks object.blocks = newBlocks; diff --git a/extensions/src/common/types/framework/blocks.ts b/extensions/src/common/types/framework/blocks.ts index a619bfac0..d5bcbd5e1 100644 --- a/extensions/src/common/types/framework/blocks.ts +++ b/extensions/src/common/types/framework/blocks.ts @@ -277,7 +277,7 @@ type Operation = export type ScratchBlockType = typeof BlockType[keyof typeof BlockType]; -export type BlockType2 = "reporter" | "command"; // etc, use actual version +export type BlockType2 = "reporter" | "command" | "hat"; // etc, use actual version export type ArgValue = any; export type ArgIdentifier = string | number; export type ArgEntry = { From f7fb0147dee56179edc8e01b89ad67a0c37c41a1 Mon Sep 17 00:00:00 2001 From: ymayarajan3 Date: Fri, 28 Jun 2024 19:41:03 -0400 Subject: [PATCH 16/24] button support --- .../extension/mixins/base/scratchVersioning/index.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts index 4cbf1aa29..c99e991b0 100644 --- a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts @@ -294,7 +294,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { newBlocks[newBlock.id] = newBlock; } // If we need to create a command from a reporter - else if (originalType == "reporter" && (blocksInfo[blockInfoIndex].blockType == "command" || blocksInfo[blockInfoIndex].blockType == "hat")) { // reporter to command) { // circle to square + else if (originalType == "reporter" && (blocksInfo[blockInfoIndex].blockType == "command" || blocksInfo[blockInfoIndex].blockType == "button" || blocksInfo[blockInfoIndex].blockType == "hat")) { // reporter to command) { // circle to square // If the previous reporter block has a parent if (object.blocks[block.parent]) { const parentBlock = object.blocks[block.parent]; @@ -317,8 +317,11 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { parentBlock.inputs[key] = values; } // Update the current block to be a command block - block.parent = null; - block.topLevel = true; + if (blocksInfo[blockInfoIndex].blockType == "command") { + block.parent = null; + block.topLevel = true; + } + } } else if ((originalType == "command") && (blocksInfo[blockInfoIndex].blockType == "hat")) { if (object.blocks[block.parent]) { From 26e491d4e79a913775741660d9e7cd466e877224 Mon Sep 17 00:00:00 2001 From: ymayarajan3 Date: Mon, 1 Jul 2024 09:55:52 -0400 Subject: [PATCH 17/24] test index file --- extensions/src/simple_example/index.ts | 30 +++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/extensions/src/simple_example/index.ts b/extensions/src/simple_example/index.ts index 932207f4b..0132de65c 100644 --- a/extensions/src/simple_example/index.ts +++ b/extensions/src/simple_example/index.ts @@ -1,4 +1,4 @@ -import { ArgumentType, BlockType, BlockUtilityWithID, Environment, ExtensionMenuDisplayDetails, Language, Menu, SaveDataHandler, block, buttonBlock, extension, tryCastToArgumentType, untilTimePassed, scratch } from "$common"; +import { ArgumentType, BlockType, BlockUtilityWithID, Environment, ExtensionMenuDisplayDetails, Language, Menu, SaveDataHandler, block, buttonBlock, extension, tryCastToArgumentType, untilTimePassed, scratch, versions, VersionedArgTransformer } from "$common"; import jibo from "./jibo.png"; import five from "./five.png"; @@ -16,6 +16,7 @@ const details: ExtensionMenuDisplayDetails = { tags: ["PRG Internal"], } + export default class SimpleTypescript extends extension(details, "ui", "customSaveData", "indicators") { count: number = 0; value: number = 4; @@ -51,6 +52,33 @@ export default class SimpleTypescript extends extension(details, "ui", "customSa console.log(value); } + // @versions({ + // type: "command", + // text: `Set selfie image as costume`, + // }) + // setCostume() { + // // this.addCostume(target, this.lastProcessedImage, "add and set") + // } + + @(scratch.command`Test ${"number"} and ${"number"} and ${"number"} and ${"number"}`) + @(versions([ + {transform: ( ({arg}) => [arg('0'), arg('1'), { value: 4 }, arg('2')] ) satisfies VersionedArgTransformer, + previousName: 'returnValue2', + previousType: 'reporter' + }, + {transform: ( ({arg}) => [arg('1'), arg('0'), arg('2'), arg('3')] ) satisfies VersionedArgTransformer, + } + ])) + async returnValue(x: number, y: number, z: number, a: number) { + //return true; + } + + + // @(scratch.reporter`Oop ${"number"} and ${"number"} and ${"number"}`) + // returnValue2(x: number, y: number, z: number) { + // return 10; + // } + @(scratch.command` Indicate ${{ type: "string", defaultValue: "Howdy!" }} as ${{ type: "string", options: ["error", "success", "warning"] }} From a9124cf98b7965215ed23c3dd4c1d07f39dfae27 Mon Sep 17 00:00:00 2001 From: ymayarajan3 Date: Mon, 1 Jul 2024 12:32:11 -0400 Subject: [PATCH 18/24] string --- .../src/common/extension/mixins/base/scratchVersioning/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts index c99e991b0..3bb554f7a 100644 --- a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts @@ -193,7 +193,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { map.set(totalList[i].id, totalList[i]); } const mechanism: VersionArgTransformMechanism = { - arg: (identifier: ArgIdentifier) => map.get(identifier), + arg: (identifier: ArgIdentifier) => map.get(String(identifier)), args: () => Array.from(map.values()), } // Complete the transformation From 8718fd8c88f5d8437d84d0cc9678744fbad8d2f2 Mon Sep 17 00:00:00 2001 From: ymayarajan3 Date: Mon, 8 Jul 2024 23:36:36 -0400 Subject: [PATCH 19/24] menu support --- .../mixins/base/scratchVersioning/index.ts | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts index 3bb554f7a..3edb6c74c 100644 --- a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts @@ -175,7 +175,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { // Remove the image entries from the arguments const blockArgs = this.removeImageEntries(blocksInfo[blockInfoIndex].arguments); // Gather values - let { inputs, variables } = this.gatherInputs(block); + let { inputs, variables, menus } = this.gatherInputs(block, object.blocks); let fields = this.gatherFields(block, blockArgs); let totalList = inputs.concat(fields); const newInputs = {}; @@ -202,7 +202,14 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { const { newEntries, mappings } = this.updateEntries(entries); totalList = newEntries; // Update variable positions - variables = this.updateDictionary(variables, mappings) + for (const key of Object.keys(menus)) { + if (mappings[key]) { + let value = newEntries[mappings[key]].value; + object.blocks[menus[key]].fields["0"][0] = value; + } + } + menus = this.updateDictionary(menus, mappings); + variables = this.updateDictionary(variables, mappings); } if (versions[i].previousType) { if (first) { @@ -216,7 +223,9 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { for (let i = 0; i < Object.keys(blockArgs).length; i++) { const argIndex = Object.keys(blockArgs)[i]; // If there's a variable in the argIndex position - if (Object.keys(variables).includes(argIndex)) { + if (Object.keys(menus).includes(argIndex)) { + newInputs[argIndex] = [1, menus[argIndex]]; + } else if (Object.keys(variables).includes(argIndex)) { newInputs[argIndex] = variables[argIndex]; } // If we need to place the value in a field @@ -455,8 +464,9 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { * @return {ArgEntry[]} return.inputs - An array of ArgEntry objects with each value representing an input * @return {object} return.variables - A dictionary with all the block's variables as values and their positions as keys */ - gatherInputs(blockJSON: any): any { + gatherInputs(blockJSON: any, blocks): any { const variables = {}; + const menu = {}; const args: ArgEntry[] = []; // Loop through the block's inputs if (blockJSON.inputs && Object.keys(blockJSON.inputs).length > 0) { @@ -465,13 +475,24 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { input = blockJSON.inputs[input]; // If there is a variable in the input if (typeof input[1] == "string") { + const variableBlock = blocks[input[1]]; + if (variableBlock) { + if (variableBlock.opcode.includes("_menu_")) { + menu[keyIndex] = input[1]; + const menuValue = variableBlock.fields["0"][0]; + args.push({id: keyIndex, value: menuValue}); + } else { + variables[keyIndex] = [ + 3, + input[1], + input[2] + ]; + args.push({id: keyIndex, value: input[2][1]}); + } + } // Set the variables dictionary accordingly - variables[keyIndex] = [ - 3, - input[1], - input[2] - ] - args.push({id: keyIndex, value: input[2][1]}); + + } else { // If the input is a value, push that value according to type const type = parseFloat(input[1][0]); @@ -496,7 +517,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { } }) } - return { inputs: args, variables: variables }; + return { inputs: args, variables: variables, menus: menu }; } /** From eaf9cda07ec465307cb33f4b14fc71e0814a5c61 Mon Sep 17 00:00:00 2001 From: ymayarajan3 Date: Mon, 8 Jul 2024 23:41:34 -0400 Subject: [PATCH 20/24] comment cleanup --- .../mixins/base/scratchVersioning/index.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts index 3edb6c74c..f8d35d8f4 100644 --- a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts @@ -201,14 +201,16 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { // Update the ArgEntry objects' IDs and get position mappings const { newEntries, mappings } = this.updateEntries(entries); totalList = newEntries; - // Update variable positions + // Change the menu block value if applicable for (const key of Object.keys(menus)) { if (mappings[key]) { let value = newEntries[mappings[key]].value; object.blocks[menus[key]].fields["0"][0] = value; } } + // Update menu positions menus = this.updateDictionary(menus, mappings); + // Update variable positions variables = this.updateDictionary(variables, mappings); } if (versions[i].previousType) { @@ -222,10 +224,12 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { // Re-create the project JSON inputs/fields with the new values for (let i = 0; i < Object.keys(blockArgs).length; i++) { const argIndex = Object.keys(blockArgs)[i]; - // If there's a variable in the argIndex position + // If there's a menu block in the argIndex position if (Object.keys(menus).includes(argIndex)) { newInputs[argIndex] = [1, menus[argIndex]]; - } else if (Object.keys(variables).includes(argIndex)) { + } + // If there's a variable in the argIndex position + else if (Object.keys(variables).includes(argIndex)) { newInputs[argIndex] = variables[argIndex]; } // If we need to place the value in a field @@ -477,11 +481,14 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { if (typeof input[1] == "string") { const variableBlock = blocks[input[1]]; if (variableBlock) { + // Store the block ID if the variable is a menu block if (variableBlock.opcode.includes("_menu_")) { menu[keyIndex] = input[1]; + // Find the menu block value const menuValue = variableBlock.fields["0"][0]; args.push({id: keyIndex, value: menuValue}); } else { + // Set the variables dictionary accordingly variables[keyIndex] = [ 3, input[1], @@ -490,9 +497,6 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { args.push({id: keyIndex, value: input[2][1]}); } } - // Set the variables dictionary accordingly - - } else { // If the input is a value, push that value according to type const type = parseFloat(input[1][0]); From 37ae54c17ee5f89b46fb73dfb50dc41abd40b2db Mon Sep 17 00:00:00 2001 From: "Github Action (authored by pmalacho-mit)" Date: Sun, 14 Jul 2024 23:37:42 -0700 Subject: [PATCH 21/24] trying to update teachable machine extension --- extensions/src/teachableMachine/index.ts | 9 +- extensions/src/teachableMachine/package.json | 12 +-- .../src/teachableMachine/pnpm-lock.yaml | 96 +++++++++---------- 3 files changed, 57 insertions(+), 60 deletions(-) diff --git a/extensions/src/teachableMachine/index.ts b/extensions/src/teachableMachine/index.ts index 13d4e35d4..6fa60eff3 100644 --- a/extensions/src/teachableMachine/index.ts +++ b/extensions/src/teachableMachine/index.ts @@ -1,4 +1,4 @@ -import { Environment, buttonBlock, extension } from "$common"; +import { Environment, buttonBlock, extension, scratch, versions } from "$common"; import tmImage from '@teachablemachine/image'; import tmPose from '@teachablemachine/pose'; import { create } from '@tensorflow-models/speech-commands'; @@ -317,7 +317,12 @@ export default class teachableMachine extends extension({ window.open('https://teachablemachine.withgoogle.com/train', '_blank'); } - @legacyBlock.useModelBlock() + @(scratch.command`use model ${"string"}`) + @versions( + { + transform: ({ arg }) => [arg("MODEL_URL")], + } + ) useModelBlock(url: string) { this.useModel(url); } diff --git a/extensions/src/teachableMachine/package.json b/extensions/src/teachableMachine/package.json index 371e2c892..706babeae 100644 --- a/extensions/src/teachableMachine/package.json +++ b/extensions/src/teachableMachine/package.json @@ -13,15 +13,7 @@ "dependencies": { "@teachablemachine/image": "^0.8.5", "@teachablemachine/pose": "^0.8.6", - "@tensorflow/tfjs": "^1.5.1", - "@tensorflow-models/speech-commands": "0.4.2" - }, - "overrides": { - "@teachablemachine/image": { - "@tensorflow/tfjs": "$@tensorflow/tfjs" - }, - "@teachablemachine/pose": { - "@tensorflow/tfjs": "$@tensorflow/tfjs" - } + "@tensorflow/tfjs": "1.3.1", + "@tensorflow-models/speech-commands": "0.4.0" } } \ No newline at end of file diff --git a/extensions/src/teachableMachine/pnpm-lock.yaml b/extensions/src/teachableMachine/pnpm-lock.yaml index ece388d09..f73f5c123 100644 --- a/extensions/src/teachableMachine/pnpm-lock.yaml +++ b/extensions/src/teachableMachine/pnpm-lock.yaml @@ -10,16 +10,16 @@ importers: dependencies: '@teachablemachine/image': specifier: ^0.8.5 - version: 0.8.5(@tensorflow/tfjs@1.7.4(seedrandom@3.0.5)) + version: 0.8.5(@tensorflow/tfjs@1.3.1(seedrandom@3.0.5)) '@teachablemachine/pose': specifier: ^0.8.6 - version: 0.8.6(@tensorflow/tfjs-converter@1.7.4(@tensorflow/tfjs-core@1.7.4))(@tensorflow/tfjs-core@1.7.4)(@tensorflow/tfjs@1.7.4(seedrandom@3.0.5)) + version: 0.8.6(@tensorflow/tfjs-converter@1.3.1(@tensorflow/tfjs-core@1.3.1))(@tensorflow/tfjs-core@1.3.1)(@tensorflow/tfjs@1.3.1(seedrandom@3.0.5)) '@tensorflow-models/speech-commands': - specifier: 0.4.2 - version: 0.4.2(@tensorflow/tfjs@1.7.4(seedrandom@3.0.5)) + specifier: 0.4.0 + version: 0.4.0(@tensorflow/tfjs@1.3.1(seedrandom@3.0.5)) '@tensorflow/tfjs': - specifier: ^1.5.1 - version: 1.7.4(seedrandom@3.0.5) + specifier: 1.3.1 + version: 1.3.1(seedrandom@3.0.5) packages: @@ -39,39 +39,39 @@ packages: '@tensorflow/tfjs-converter': ^3.0.0-rc.1 '@tensorflow/tfjs-core': ^3.0.0-rc.1 - '@tensorflow-models/speech-commands@0.4.2': - resolution: {integrity: sha512-XpfRUzVy0DtQkMvR/jqRWCFgcka9pgwEdfqtHTqnIBoueAUJiuq1edD+RNzbK01O3bAJ9FIL/VuiTZmxNx92NQ==} + '@tensorflow-models/speech-commands@0.4.0': + resolution: {integrity: sha512-OtG9QElQwts9j+gF2ZfyjG8xZa9rZq600Op9LhUYi59OvhuCSpof3vkwLP3oyMhMOrEFfAn546dHuA5dLfdK1A==} peerDependencies: - '@tensorflow/tfjs': ^1.5.2 + '@tensorflow/tfjs': ^1.3.0 - '@tensorflow/tfjs-converter@1.7.4': - resolution: {integrity: sha512-B/Ux9I3osI0CXoESGR0Xe5C6BsEfC04+g2xn5zVaW9KEuVEnGEgnuBQxgijRFzkqTwoyLv4ptAmjyIghVARX0Q==} + '@tensorflow/tfjs-converter@1.3.1': + resolution: {integrity: sha512-8IHzcIZwqCofcRBhaCTN6W73m2RGdBDH6BEORf0KDdbgbz4DouItVcKX692PrA8gchY+Xy8ZHMpj93PcAOs17g==} peerDependencies: - '@tensorflow/tfjs-core': 1.7.4 + '@tensorflow/tfjs-core': 1.3.1 - '@tensorflow/tfjs-core@1.7.4': - resolution: {integrity: sha512-3G4VKJ6nPs7iCt6gs3bjRj8chihKrYWenf63R0pm7D9MhlrVoX/tpN4LYVMGgBL7jHPxMLKdOkoAZJrn/J88HQ==} + '@tensorflow/tfjs-core@1.3.1': + resolution: {integrity: sha512-X4MKhpg1gLEZetKUMeQNW6diP3gbFFddeF6UT816sH8jOenX/8x2HnVmANpNnUxCTPhDniY3V9zhBWwbl13+Yg==} engines: {yarn: '>= 1.3.2'} - '@tensorflow/tfjs-data@1.7.4': - resolution: {integrity: sha512-WFYG9wWjNDi62x6o3O20Q0XJxToCw2J4/fBEXiK/Gr0hIqVhl2oLQ1OjTWq7O08NUxM6BRzuG+ra3gWYdQUzOw==} + '@tensorflow/tfjs-data@1.3.1': + resolution: {integrity: sha512-GCATkRXKBewLdri+qfzTLcVsToX9W7fz5JwApY6Ysel/aTtKSYArU8+OppUKAfWkczBA/fZKlJTONnqC42H6bA==} peerDependencies: - '@tensorflow/tfjs-core': 1.7.4 + '@tensorflow/tfjs-core': 1.3.1 seedrandom: ~2.4.3 - '@tensorflow/tfjs-layers@1.7.4': - resolution: {integrity: sha512-5/K8Z8RBfXsucL6EaSeb3/8jB/I8oPaaXkxwKVsBPQ+u6lB6LEtSKzeiFc57nDr5OMtVaUZV+pKDNEzP0RUQlg==} + '@tensorflow/tfjs-layers@1.3.1': + resolution: {integrity: sha512-LiC9mGU9ZQ7+c3PiQ6MDkvT/gZgqNN67TsypfKm+L44u2TLtMZf77mRaQ/8rW/F89Ey4PDDFftDRDYv0t3fukw==} peerDependencies: - '@tensorflow/tfjs-core': 1.7.4 + '@tensorflow/tfjs-core': 1.3.1 - '@tensorflow/tfjs@1.7.4': - resolution: {integrity: sha512-XWGwRQ/ECEoQacd74JY/dmbLdnMpwtq3H8tls45dQ+GJ553Advir1FDo/aQt0Yr6fTimQDeiOIG4Mcb5KduP/w==} + '@tensorflow/tfjs@1.3.1': + resolution: {integrity: sha512-eJ8+WYRYYx6o3R4daV3T46ZHI/FN0+wslNnobh6d2Sp/T8PfaFpdR38nRrYtxnz33A80B2pMk4VxzoVivS4/jA==} '@types/node-fetch@2.6.11': resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} - '@types/node@20.12.12': - resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==} + '@types/node@20.14.10': + resolution: {integrity: sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==} '@types/offscreencanvas@2019.3.0': resolution: {integrity: sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==} @@ -130,36 +130,36 @@ packages: snapshots: - '@teachablemachine/image@0.8.5(@tensorflow/tfjs@1.7.4(seedrandom@3.0.5))': + '@teachablemachine/image@0.8.5(@tensorflow/tfjs@1.3.1(seedrandom@3.0.5))': dependencies: - '@tensorflow/tfjs': 1.7.4(seedrandom@3.0.5) + '@tensorflow/tfjs': 1.3.1(seedrandom@3.0.5) autobind-decorator: 2.4.0 seedrandom: 2.4.4 - '@teachablemachine/pose@0.8.6(@tensorflow/tfjs-converter@1.7.4(@tensorflow/tfjs-core@1.7.4))(@tensorflow/tfjs-core@1.7.4)(@tensorflow/tfjs@1.7.4(seedrandom@3.0.5))': + '@teachablemachine/pose@0.8.6(@tensorflow/tfjs-converter@1.3.1(@tensorflow/tfjs-core@1.3.1))(@tensorflow/tfjs-core@1.3.1)(@tensorflow/tfjs@1.3.1(seedrandom@3.0.5))': dependencies: - '@tensorflow-models/posenet': 2.2.2(@tensorflow/tfjs-converter@1.7.4(@tensorflow/tfjs-core@1.7.4))(@tensorflow/tfjs-core@1.7.4) - '@tensorflow/tfjs': 1.7.4(seedrandom@3.0.5) + '@tensorflow-models/posenet': 2.2.2(@tensorflow/tfjs-converter@1.3.1(@tensorflow/tfjs-core@1.3.1))(@tensorflow/tfjs-core@1.3.1) + '@tensorflow/tfjs': 1.3.1(seedrandom@3.0.5) autobind-decorator: 2.4.0 seedrandom: 3.0.5 transitivePeerDependencies: - '@tensorflow/tfjs-converter' - '@tensorflow/tfjs-core' - '@tensorflow-models/posenet@2.2.2(@tensorflow/tfjs-converter@1.7.4(@tensorflow/tfjs-core@1.7.4))(@tensorflow/tfjs-core@1.7.4)': + '@tensorflow-models/posenet@2.2.2(@tensorflow/tfjs-converter@1.3.1(@tensorflow/tfjs-core@1.3.1))(@tensorflow/tfjs-core@1.3.1)': dependencies: - '@tensorflow/tfjs-converter': 1.7.4(@tensorflow/tfjs-core@1.7.4) - '@tensorflow/tfjs-core': 1.7.4 + '@tensorflow/tfjs-converter': 1.3.1(@tensorflow/tfjs-core@1.3.1) + '@tensorflow/tfjs-core': 1.3.1 - '@tensorflow-models/speech-commands@0.4.2(@tensorflow/tfjs@1.7.4(seedrandom@3.0.5))': + '@tensorflow-models/speech-commands@0.4.0(@tensorflow/tfjs@1.3.1(seedrandom@3.0.5))': dependencies: - '@tensorflow/tfjs': 1.7.4(seedrandom@3.0.5) + '@tensorflow/tfjs': 1.3.1(seedrandom@3.0.5) - '@tensorflow/tfjs-converter@1.7.4(@tensorflow/tfjs-core@1.7.4)': + '@tensorflow/tfjs-converter@1.3.1(@tensorflow/tfjs-core@1.3.1)': dependencies: - '@tensorflow/tfjs-core': 1.7.4 + '@tensorflow/tfjs-core': 1.3.1 - '@tensorflow/tfjs-core@1.7.4': + '@tensorflow/tfjs-core@1.3.1': dependencies: '@types/offscreencanvas': 2019.3.0 '@types/seedrandom': 2.4.27 @@ -168,32 +168,32 @@ snapshots: node-fetch: 2.1.2 seedrandom: 2.4.3 - '@tensorflow/tfjs-data@1.7.4(@tensorflow/tfjs-core@1.7.4)(seedrandom@3.0.5)': + '@tensorflow/tfjs-data@1.3.1(@tensorflow/tfjs-core@1.3.1)(seedrandom@3.0.5)': dependencies: - '@tensorflow/tfjs-core': 1.7.4 + '@tensorflow/tfjs-core': 1.3.1 '@types/node-fetch': 2.6.11 node-fetch: 2.1.2 seedrandom: 3.0.5 - '@tensorflow/tfjs-layers@1.7.4(@tensorflow/tfjs-core@1.7.4)': + '@tensorflow/tfjs-layers@1.3.1(@tensorflow/tfjs-core@1.3.1)': dependencies: - '@tensorflow/tfjs-core': 1.7.4 + '@tensorflow/tfjs-core': 1.3.1 - '@tensorflow/tfjs@1.7.4(seedrandom@3.0.5)': + '@tensorflow/tfjs@1.3.1(seedrandom@3.0.5)': dependencies: - '@tensorflow/tfjs-converter': 1.7.4(@tensorflow/tfjs-core@1.7.4) - '@tensorflow/tfjs-core': 1.7.4 - '@tensorflow/tfjs-data': 1.7.4(@tensorflow/tfjs-core@1.7.4)(seedrandom@3.0.5) - '@tensorflow/tfjs-layers': 1.7.4(@tensorflow/tfjs-core@1.7.4) + '@tensorflow/tfjs-converter': 1.3.1(@tensorflow/tfjs-core@1.3.1) + '@tensorflow/tfjs-core': 1.3.1 + '@tensorflow/tfjs-data': 1.3.1(@tensorflow/tfjs-core@1.3.1)(seedrandom@3.0.5) + '@tensorflow/tfjs-layers': 1.3.1(@tensorflow/tfjs-core@1.3.1) transitivePeerDependencies: - seedrandom '@types/node-fetch@2.6.11': dependencies: - '@types/node': 20.12.12 + '@types/node': 20.14.10 form-data: 4.0.0 - '@types/node@20.12.12': + '@types/node@20.14.10': dependencies: undici-types: 5.26.5 From dacd4e3c7ba4ffdbf26164704d0d8b70d321e4da Mon Sep 17 00:00:00 2001 From: "Github Action (authored by pmalacho-mit)" Date: Sun, 14 Jul 2024 23:44:43 -0700 Subject: [PATCH 22/24] spread transformers --- extensions/src/common/extension/decorators/blocks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/src/common/extension/decorators/blocks.ts b/extensions/src/common/extension/decorators/blocks.ts index 7ecbf833f..332cf7e54 100644 --- a/extensions/src/common/extension/decorators/blocks.ts +++ b/extensions/src/common/extension/decorators/blocks.ts @@ -94,7 +94,7 @@ export function versions< const Fn extends (...args: Args) => Return, > ( - config: VersionedOptions[] + ...config: VersionedOptions[] ): TypedMethodDecorator Return> { return function (this: This, target: (this: This, ...args: Args) => Return, context: ClassMethodDecoratorContext) { From d3d72a1455011cb7891475fe7d81f81cd8629e80 Mon Sep 17 00:00:00 2001 From: ymayarajan3 Date: Mon, 15 Jul 2024 17:18:05 -0400 Subject: [PATCH 23/24] changing list to dictionary --- .../mixins/base/scratchVersioning/index.ts | 64 +++++++++++-------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts index f8d35d8f4..f530f18a8 100644 --- a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts @@ -176,22 +176,19 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { const blockArgs = this.removeImageEntries(blocksInfo[blockInfoIndex].arguments); // Gather values let { inputs, variables, menus } = this.gatherInputs(block, object.blocks); - let fields = this.gatherFields(block, blockArgs); - let totalList = inputs.concat(fields); + let fields = this.gatherFields(block); + let totalList = this.mergeMaps(inputs, fields); const newInputs = {}; const newFields = {}; let changed = false; let moveToSay = false; // Apply each version modification as needed - for (let i = version; i < versions.length; i++) { if (versions[i].transform) { - // Create the map to be used in the mechanism from ArgEntry objects - const map = new Map(); - for (let i = 0; i < totalList.length; i++) { - map.set(totalList[i].id, totalList[i]); - } + // totalList is the map to be used in the mechanism from ArgEntry objects + const map: any = totalList; + let originalKeys: string[] = Array.from(map.keys()); const mechanism: VersionArgTransformMechanism = { arg: (identifier: ArgIdentifier) => map.get(String(identifier)), args: () => Array.from(map.values()), @@ -199,12 +196,12 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { // Complete the transformation const entries: ArgEntry[] = versions[i].transform(mechanism); // Update the ArgEntry objects' IDs and get position mappings - const { newEntries, mappings } = this.updateEntries(entries); + const { newEntries, mappings } = this.updateEntries(entries, originalKeys); totalList = newEntries; // Change the menu block value if applicable for (const key of Object.keys(menus)) { if (mappings[key]) { - let value = newEntries[mappings[key]].value; + let value = newEntries.get(mappings[key]).value; object.blocks[menus[key]].fields["0"][0] = value; } } @@ -234,7 +231,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { } // If we need to place the value in a field else if (blockArgs[argIndex].menu) { - let fieldValue = totalList[argIndex]; + let fieldValue = totalList.get(argIndex).value; if (typeof fieldValue == "number") { fieldValue = String(fieldValue); } @@ -250,7 +247,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { newInputs[argIndex] = [ 1, [ typeNum, - String(totalList[argIndex].value) + String(totalList.get(argIndex).value) ] ] } @@ -386,21 +383,22 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { * assign each ArgEntry object a new ID * * @param {ArgEntry[]} entries The updated entries to create the mapping from + * @param {string[]} originalKeys The original list of keys to be passed in * @return {ArgEntry[]} returns.newEntries - The ArgEntry objects with updated IDs * @return {object} returns.mappings - A dictionary with the position mappings */ - updateEntries(entries: ArgEntry[]) { + updateEntries(entries: ArgEntry[], originalKeys: string[]) { const mappings = {}; - let newEntries = []; + let newEntries = new Map(); // Loop through the arg entries for (let i = 0; i < entries.length; i++) { // If the value hasn't just been added if (entries[i].id) { // Set the positional dictionary - mappings[entries[i].id] = String(i); + mappings[entries[i].id] = originalKeys[i]; } // Update the new ArgEntry array with the new ID - newEntries.push({id: String(i), value: entries[i].value}); + newEntries.set(originalKeys[i], {id: originalKeys[i], value: entries[i].value}); } return { newEntries, mappings }; @@ -471,7 +469,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { gatherInputs(blockJSON: any, blocks): any { const variables = {}; const menu = {}; - const args: ArgEntry[] = []; + const args = new Map(); // Loop through the block's inputs if (blockJSON.inputs && Object.keys(blockJSON.inputs).length > 0) { Object.keys(blockJSON.inputs).forEach(input => { @@ -486,7 +484,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { menu[keyIndex] = input[1]; // Find the menu block value const menuValue = variableBlock.fields["0"][0]; - args.push({id: keyIndex, value: menuValue}); + args.set(keyIndex, menuValue); } else { // Set the variables dictionary accordingly variables[keyIndex] = [ @@ -494,7 +492,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { input[1], input[2] ]; - args.push({id: keyIndex, value: input[2][1]}); + args.set(keyIndex, {id: keyIndex, value: input[2][1]}); } } } else { @@ -508,13 +506,13 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { case 9: // COLOR_PICKER_PRIMITIVE case 4: // MATH_NUM_PRIMITIVE { - args.push({id: keyIndex, value: parseFloat(input[1][1])}); + args.set(keyIndex, {id: keyIndex, value: parseFloat(input[1][1])}); break; } case 10: // TEXT_PRIMITIVE default: // BROADCAST_PRIMITIVE, VAR_PRIMITIVE, LIST_PRIMITIVE { - args.push({id: keyIndex, value: input[1][1]}) + args.set(keyIndex, {id: keyIndex, value: input[1][1]}) break; } } @@ -530,8 +528,8 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { * @param {object} blockJSON The block to gather the inputs from * @return {ArgEntry[]} An array of ArgEntry objects representing each field */ - gatherFields(blockJSON: any, argList: any) { - const args: ArgEntry[] = []; + gatherFields(blockJSON: any): any { + const args = new Map(); // Loop through each field if (blockJSON.fields && Object.keys(blockJSON.fields).length > 0) { Object.keys(blockJSON.fields).forEach(field => { @@ -539,17 +537,27 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { field = blockJSON.fields[field]; // Collect the field's value let value = (field as any).value; - let argType = argList[(field as any).name].type; + // let argType = argList[(field as any).name].type; // Convert the value to its correct type - if (argType == "number" || argType == "angle") { - value = parseFloat(value); - } - args.push({id: keyIndex, value: value}) + // if (argType == "number" || argType == "angle") { + // value = parseFloat(value); + // } + args.set(keyIndex, {id: keyIndex, value: value}) }) } return args; } + mergeMaps(map1: any, map2: any): any { + let combinedMap = new Map([...map1]); + + map2.forEach((value, key) => { + combinedMap.set(key, value); + }); + + return combinedMap; + } + /** * If the positions of the arguments change between version transformations, we * use this function to update the variables dictionary to contain each variable's From 2c513a28f1c887bc8e9eaa580a864cd6a631e3b4 Mon Sep 17 00:00:00 2001 From: ymayarajan3 Date: Mon, 15 Jul 2024 18:16:06 -0400 Subject: [PATCH 24/24] mystery array --- .../extension/mixins/base/scratchVersioning/index.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts index f530f18a8..03dbe9bd9 100644 --- a/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchVersioning/index.ts @@ -136,7 +136,6 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { // Check if version name is included const regex = /_v(\d+)/g; const matches = blockOpcode.match(regex); // Get all matches - // Collect the version number if (matches) { const lastMatch = matches[matches.length - 1]; @@ -154,6 +153,8 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { return acc; }, {}); + + const block = object.blocks[blockId]; // If the block is under the current extension if (extensionID == this.getInfo().id && !block.opcode.includes("_menu_")) { @@ -165,7 +166,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { } // Update the opcode to be the current version name block.opcode = block.opcode.replace(oldIndex, blockInfoIndex); - const versions = this.getVersion(blockInfoIndex); + let versions = this.getVersion(blockInfoIndex); let originalType = blocksInfo[blockInfoIndex].blockType; let first = true; @@ -184,6 +185,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { let moveToSay = false; // Apply each version modification as needed + versions = Array.isArray(versions) && Array.isArray(versions[0]) ? versions[0] : versions; for (let i = version; i < versions.length; i++) { if (versions[i].transform) { // totalList is the map to be used in the mechanism from ArgEntry objects @@ -194,6 +196,7 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { args: () => Array.from(map.values()), } // Complete the transformation + const entries: ArgEntry[] = versions[i].transform(mechanism); // Update the ArgEntry objects' IDs and get position mappings const { newEntries, mappings } = this.updateEntries(entries, originalKeys); @@ -417,7 +420,8 @@ export default function (Ctor: BaseScratchExtensionConstuctor) { // Loop through every block in the extension for (const opcode of Object.keys(blocksInfo)) { // Get version information for each extension - const versions = this.getVersion(opcode); + let versions = this.getVersion(opcode); + versions = Array.isArray(versions) && Array.isArray(versions[0]) ? versions[0] : versions // If there is version information if (versions && versions.length > 0) { let tempName = opcode;