diff --git a/extensions/src/common/extension/decorators/blocks.ts b/extensions/src/common/extension/decorators/blocks.ts index deb608fb9..0cafcd24c 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, Config } from "$common/types"; import { getImplementationName } from "../mixins/base/scratchInfo/index"; import { ExtensionInstance } from ".."; import { isFunction, isString, tryCreateBundleTimeEvent } from "$common/utils"; @@ -55,36 +55,44 @@ export const setAccessorPrefix = "__setter__"; * @returns A manipulated version of the original method that is */ + export function block< const This extends ExtensionInstance, const Args extends any[], const Return, const Fn extends (...args: Args) => Return, - const TRemoveUtil extends any[] = Args extends [...infer R extends any[], BlockUtility] ? R : Args, + const TRemoveUtil extends any[] = Args extends [...infer R extends any[], BlockUtility] ? R : Args > ( blockInfoOrGetter: (BlockMetadata<(...args: TRemoveUtil) => Return> | ((this: This, self: This) => BlockMetadata<(...args: TRemoveUtil) => Return>)) - ): TypedMethodDecorator Return> { + , versions?: Config): TypedMethodDecorator Return> { return function (this: This, target: (this: This, ...args: Args) => Return, context: ClassMethodDecoratorContext) { - const opcode = target.name; - const internalFuncName = getImplementationName(opcode); - // could add check for if this block is meant for scratch - context.addInitializer(function () { this.pushBlock(opcode, blockInfoOrGetter, target) }); - - const isProbableAtBundleTime = !isFunction(blockInfoOrGetter); - if (isProbableAtBundleTime) { - const { type } = blockInfoOrGetter; - blockBundleEvent?.fire({ - methodName: opcode, - args: extractArgs(blockInfoOrGetter).map(a => isString(a) ? a : a.type), - // is 'any' an issue? Likely! - returns: type === "command" ? "void" : type === "Boolean" ? "bool" : "any", - scratchType: blockInfoOrGetter.type - }); - } - - return (function () { return this[internalFuncName].call(this, ...arguments) }); + var opcode = target.name; + const internalFuncName = getImplementationName(opcode); + // could add check for if this block is meant for scratch + if (versions) { + context.addInitializer(function () { this.pushBlock(opcode, blockInfoOrGetter, target, versions) }); + } else { + context.addInitializer(function () { this.pushBlock(opcode, blockInfoOrGetter, target) }); + } + + + const isProbableAtBundleTime = !isFunction(blockInfoOrGetter); + if (isProbableAtBundleTime) { + const { type } = blockInfoOrGetter; + blockBundleEvent?.fire({ + methodName: opcode, + args: extractArgs(blockInfoOrGetter).map(a => isString(a) ? a : a.type), + // is 'any' an issue? Likely! + returns: type === "command" ? "void" : type === "Boolean" ? "bool" : "any", + scratchType: blockInfoOrGetter.type + }); + } + return (function () { return this[internalFuncName].call(this, ...arguments) }); + + + }; } diff --git a/extensions/src/common/extension/decorators/newBlocks.ts b/extensions/src/common/extension/decorators/newBlocks.ts index e4069f657..fca4c6734 100644 --- a/extensions/src/common/extension/decorators/newBlocks.ts +++ b/extensions/src/common/extension/decorators/newBlocks.ts @@ -1,15 +1,20 @@ -import { BlockMetadata, Argument, ReturnTypeByBlockType, ScratchBlockType, ToArguments } from "$common/types"; +import { BlockMetadata, Config, Argument, ReturnTypeByBlockType, ScratchBlockType, ToArguments } from "$common/types"; import { block } from "$common/extension/decorators/blocks"; import { ExtensionInstance } from ".."; import { TypedMethodDecorator } from "."; import type BlockUtilityWithID from "$scratch-vm/engine/block-utility"; +export const scratch = { + reporter: makeDecorator("reporter"), + command: makeDecorator("command"), +} + const process = (type: ScratchBlockType, strings: TemplateStringsArray, ...args: any[]) => { if (args.length === 0) return { type, text: strings[0], }; const text = (...placeholders: any[]) => strings.map((str, i) => `${str}${placeholders[i] ?? ""}`).join(""); if (args.length === 1) return { type, text, arg: args[0] }; - return { type, text, args }; + return { type, text, args }; } export function makeDecorator(type: T): TemplateEngine["execute"] { @@ -20,11 +25,26 @@ export function makeDecorator(type: T): TemplateEngi const input: any = typeof builderOrStrings == "function" ? (instance) => builderOrStrings(instance, process.bind(null, type)) : process(type, builderOrStrings, ...args); - return block(input)(target, context); + if (target.config) { + return block(input, target.config)(target, context); + } else { + return block(input)(target, context); + } + } } } +export function scratchVersions(config: Config) { + return function( + target, + context + ) { + target.config = config; + return target; + } + }; + namespace Utility { export type TaggedTemplate = (strings: TemplateStringsArray, ...args: Args) => Return; } @@ -68,7 +88,4 @@ interface TemplateEngine { } -export const scratch = { - reporter: makeDecorator("reporter"), - command: makeDecorator("command"), -} \ 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 d95de0f7c..b95c9952f 100644 --- a/extensions/src/common/extension/mixins/base/scratchInfo/index.ts +++ b/extensions/src/common/extension/mixins/base/scratchInfo/index.ts @@ -1,6 +1,7 @@ import { castToType } from "$common/cast"; import CustomArgumentManager from "$common/extension/mixins/configurable/customArguments/CustomArgumentManager"; import { ArgumentType, BlockType } from "$common/types/enums"; +import { Config } from "$common/types" import { BlockOperation, ValueOf, Menu, ExtensionMetadata, ExtensionBlockMetadata, ExtensionMenuMetadata, DynamicMenu, BlockMetadata, BlockUtilityWithID, } from "$common/types"; import { registerButtonCallback } from "$common/ui"; import { isString, typesafeCall, } from "$common/utils"; @@ -23,6 +24,7 @@ const nonBlockContextError = "Block method was not given a block utility, and th const checkForBlockContext = (blockUtility: BlockUtilityWithID) => isBlockUtilityWithID(blockUtility) ? void 0 : console.error(nonBlockContextError); + /** * Wraps a blocks operation so that the arguments passed from Scratch are first extracted and then passed as indices in a parameter array. * @param _this What will be bound to the 'this' context of the underlying operation @@ -70,7 +72,7 @@ export const wrapOperation = ( * @see https://www.typescriptlang.org/docs/handbook/mixins.html */ export default function (Ctor: CustomizableExtensionConstructor) { - type BlockEntry = { definition: BlockDefinition, operation: BlockOperation }; + type BlockEntry = { definition: BlockDefinition, operation: BlockOperation, versions: Config }; type BlockMap = Map; abstract class ScratchExtension extends Ctor { private readonly blockMap: BlockMap = new Map(); @@ -84,9 +86,15 @@ export default function (Ctor: CustomizableExtensionConstructor) { * @param definition * @param operation */ - pushBlock(opcode: string, definition: BlockDefinition, operation: BlockOperation) { + // add functions parameter here + pushBlock(opcode: string, definition: BlockDefinition, operation: BlockOperation, versions?: Config) { if (this.blockMap.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.blockMap.set(opcode, { definition, operation } as BlockEntry); + if (versions) { + this.blockMap.set(opcode, { definition, operation, versions} as BlockEntry); + } else { + this.blockMap.set(opcode, { definition, operation, versions: [] } as BlockEntry); + } + } protected getInfo(): ExtensionMetadata { @@ -103,7 +111,7 @@ export default function (Ctor: CustomizableExtensionConstructor) { private convertToInfo(details: [opcode: string, entry: BlockEntry]) { const [opcode, entry] = details; - const { definition, operation } = entry; + const { definition, operation, versions } = entry; // Utilize explicit casting to appease test framework's typechecker const block = isBlockGetter(definition) @@ -119,7 +127,7 @@ export default function (Ctor: CustomizableExtensionConstructor) { const displayText = convertToDisplayText(opcode, text, args); const argumentsInfo = convertToArgumentInfo(opcode, args, menus); - const info: ExtensionBlockMetadata = { opcode, text: displayText, blockType: type, arguments: argumentsInfo }; + const info: ExtensionBlockMetadata = { opcode, text: displayText, blockType: type, arguments: argumentsInfo, versions }; if (type === BlockType.Button) { const buttonID = getButtonID(id, opcode); diff --git a/extensions/src/common/types/framework/blocks.ts b/extensions/src/common/types/framework/blocks.ts index 3dbfa3d6b..c9ec87169 100644 --- a/extensions/src/common/types/framework/blocks.ts +++ b/extensions/src/common/types/framework/blocks.ts @@ -9,8 +9,16 @@ export type ButtonBlock = () => InternalButtonKey; export type BlockMetadata< Fn extends BlockOperation, + TFunctions extends Array<(...args: any[]) => any> = [], TParameters extends any[] = Parameters extends [...infer R, BlockUtility] ? R : Parameters -> = Type> & Text & Arguments; +> = Type> & Text & Arguments & { + optionalFunctions?: TFunctions; +}; + +export type ArgTransformer = (...args: any[]) => any[]; +export type Config = { + [index: number]: ArgTransformer; +}; export type Block = BlockMetadata & Operation; diff --git a/extensions/src/common/types/legacy.ts b/extensions/src/common/types/legacy.ts index fb36aba20..05758a686 100644 --- a/extensions/src/common/types/legacy.ts +++ b/extensions/src/common/types/legacy.ts @@ -1,5 +1,6 @@ import { ArgumentType, BlockType } from "./enums"; import { ValueOf } from "./utils"; +import { Config } from "." // Type definitions for scratch-vm (extension environment) 3.0 // Project: https://github.com/LLK/scratch-vm#readme @@ -131,6 +132,8 @@ export interface ExtensionBlockMetadata { /** Map of argument placeholder to metadata about each arg. */ arguments?: Record | undefined; + + versions?: Config; } /** All the metadata needed to register an argument for an extension block. */