diff --git a/extensions/documentation/src/anatomy/README.md b/extensions/documentation/src/anatomy/README.md index d08b3377c..90a674d05 100644 --- a/extensions/documentation/src/anatomy/README.md +++ b/extensions/documentation/src/anatomy/README.md @@ -3,7 +3,7 @@ Extensions are defined by all the files that appear in their associated directory, located within `/extensions/src/`. -This directory is created when you run the command `npm run new:extension ` from the root of the project, where the value you provide for `` is used to name this new directory. +This directory is created when you run the command `pnpm new:extension ` from the root of the project, where the value you provide for `` is used to name this new directory. > **NOTE:** It is important to keep in mind that the name of an extension's associated directory is internally used to identify it, so it is best to avoid changing the directory's name (as this could affect previously saved `.sb3` projects that reference the extension). @@ -24,11 +24,11 @@ Below are the files you should always find within an extension's directory: - For example, if your extension folder is `myExtension`, you can do the following: ``` cd extensions/myExtension # only do this once - npm run dev + pnpm dev ``` - Instead of running the following from the root of the project every time: ``` - npm run dev -- --include myExtension + pnpm dev -i myExtension ``` - Inspect the `package.json` file to see all augmented scripts. diff --git a/extensions/documentation/src/blockUtility/index.ts b/extensions/documentation/src/blockUtility/index.ts index b815a9819..6bc171c56 100644 --- a/extensions/documentation/src/blockUtility/index.ts +++ b/extensions/documentation/src/blockUtility/index.ts @@ -1,13 +1,9 @@ -import { BlockUtilityWithID, Environment, block, extension } from "$common"; +import { BlockUtilityWithID, Environment, extension, scratch } from "$common"; export default class extends extension({ name: "Block Utility example" }) { override init(env: Environment) { } - @block({ - type: "command", - text: (someArgument) => `Block text with ${someArgument}`, - arg: "number" - }) + @(scratch.command`Block text with ${"number"}`) exampleBlockMethod(someArgument: number, util: BlockUtilityWithID) { const { blockID } = util; console.log(`My ID is: ${blockID}`) diff --git a/extensions/documentation/src/customArguments/README.md b/extensions/documentation/src/customArguments/README.md index c929034e4..7d3b77d18 100644 --- a/extensions/documentation/src/customArguments/README.md +++ b/extensions/documentation/src/customArguments/README.md @@ -6,7 +6,7 @@ One of the coolest is the ability to define custom arguments, which means both: - Introducing an arbitrary new type of argument - It could be an alias for a `number` the same way the built-in `Angle` argument is. Or it could be something new entirely, like an object with some specific keys, or an array of a certain length -- whatever you want! - Defining the UI the allows a user to set / interact with that argument type - - Imagine being able to create argument-specifc UI like is done for the built-in `Note`, `Angle`, `Color`, and `Matrix` arguments + - Imagine being able to create argument-specific UI like is done for the built-in `Note`, `Angle`, `Color`, and `Matrix` arguments Here's how: @@ -17,7 +17,7 @@ For a quick breakdown of how we handle UI generally in the Extension Framework, Then run the following command: ```bash -npm run add:arg +pnpm add:arg # For example: npm run add:arg myExtension ``` @@ -31,7 +31,7 @@ Assume we have the following extension: [](./extension.ts?export=x) -When invoking the `@block` decorator function on our method that uses a custom argument, we can define the `arg` field like so: +When invoking the `@scratch` decorator function on our method that uses a custom argument, we can define the placeholder like so (note the use of the `instance` parameter in invoking the `makeCustomArgument` function): [](./index.ts?export=x) @@ -63,10 +63,10 @@ At the heart of this implementation is co-opting the usage of block argument's d > In the extension framework, an argument with a dynamic menu looks like: >```ts ->arg: { +>@(scratch.command`placholder text ${{ > type: "number", > options: () => ["option A", "option B"] // for example ->} +>}}`) >``` This is the perfect setup for our solution, as: diff --git a/extensions/documentation/src/customArguments/index.ts b/extensions/documentation/src/customArguments/index.ts index d9a3cfde0..474f28649 100644 --- a/extensions/documentation/src/customArguments/index.ts +++ b/extensions/documentation/src/customArguments/index.ts @@ -1,5 +1,5 @@ import { codeSnippet, notRelevantToExample } from "documentation"; -import { block, extension } from "$common"; +import { extension, scratch } from "$common"; import { MyCustomArgument, details } from "./extension"; export const x = codeSnippet(); @@ -10,25 +10,20 @@ import MyArgUI from "./MyArgUI.svelte"; export default class ExtensionWithCustomArgument extends extension(details, "customArguments") { init = notRelevantToExample; - @block((self) => ({ - type: "command", - text: (arg) => `Set custom argument ${arg}`, - - /** Invoke the member function `makeCustomArgument` of `self` parameter - * (which is an instance of our `ExtensionWithCustomArgument` class). - * The `makeCustomArgument` function accepts an object with the following fields: - * - component: The `svelte` component that should be displayed when this argument is clicked on. - * - initial: The value that the argument should default to. NOTE that this item has both a 'text' and 'value' field. - * - This is because the value of the custom argument must be able to be represented as a string - * and displayed directly in the block once the UI closes. - * Thus, whenever you set a custom argument, you'll need to provide both a 'value' and a 'text' - * representation of that value. - */ - arg: self.makeCustomArgument({ - component: MyArgUI, - initial: { value: { a: 10, b: "Hello world", c: false }, text: "[10, Hello world, false]", } - }), - })) + /** Invoke the member function `makeCustomArgument` of `self` parameter + * (which is an instance of our `ExtensionWithCustomArgument` class). + * The `makeCustomArgument` function accepts an object with the following fields: + * - component: The `svelte` component that should be displayed when this argument is clicked on. + * - initial: The value that the argument should default to. NOTE that this item has both a 'text' and 'value' field. + * - This is because the value of the custom argument must be able to be represented as a string + * and displayed directly in the block once the UI closes. + * Thus, whenever you set a custom argument, you'll need to provide both a 'value' and a 'text' + * representation of that value. + */ + @(scratch.command((instance, $) => $`Set custom argument ${instance.makeCustomArgument({ + component: MyArgUI, + initial: { value: { a: 10, b: "Hello world", c: false }, text: "[10, Hello world, false]", } + })}`)) /** Our operation should expect an input that matches our custom argument type */ blockWithCustomArgument(custom: MyCustomArgument) { const { a, b, c } = custom; diff --git a/extensions/documentation/src/inlineImages/README.md b/extensions/documentation/src/inlineImages/README.md index bd66a13e4..e660bc4e1 100644 --- a/extensions/documentation/src/inlineImages/README.md +++ b/extensions/documentation/src/inlineImages/README.md @@ -2,7 +2,7 @@ As noted in [Scratch's extension documentation](https://github.com/scratchfoundation/scratch-vm/blob/develop/docs/extensions.md#adding-an-inline-image), Blocks support arguments that can display images inline within their text display. -We can make use of this feature within the framework by adding an extra argument of type `"inline image"` to our extension's method, and then seperately add an `arg` (or `args`) entry within the associated `@block` decorator invocation. +We can make use of this feature within the framework by adding an extra argument of type `"inline image"` to our extension's method, and then seperately add a placeholder within the associated `@scratch` decorator invocation. See the below example (which assumes that a file `myPic.png` is located in the same directory as our code): diff --git a/extensions/documentation/src/inlineImages/index.ts b/extensions/documentation/src/inlineImages/index.ts index 47ee5095b..86d7c1910 100644 --- a/extensions/documentation/src/inlineImages/index.ts +++ b/extensions/documentation/src/inlineImages/index.ts @@ -2,7 +2,7 @@ import { codeSnippet } from "documentation"; export const x = codeSnippet(); -import { Environment, block, extension } from "$common"; +import { Environment, extension, scratch } from "$common"; // We import our image as if it was a code file import myPic from "./myPic.png"; @@ -11,29 +11,26 @@ export default class ExampleExtensionWithInlineImages extends extension({ }) { override init(env: Environment) { } - @block({ - type: "command", - text: (image) => `Here's an inline image: ${image}`, - arg: { + @(scratch.command`Here's an inline image: ${ + { type: "image", uri: myPic, alt: "this is a test image", // description of the image for screen readers flipRTL: true, } - }) + }`) methodWithOnlyInlineImage(image: "inline image") { // NOTE: The `image` argument should not be used } - @block({ - type: "command", - text: (someNumber, image, someString) => `Here's a number ${someNumber} and picture ${image} and string ${someString}}`, - args: [ - { type: "number" }, - { type: "image", uri: myPic, alt: "this is a test image", flipRTL: true }, - "string" - ] - }) + @(scratch.command`Here's a number ${{ type: "number" }} and picture ${ + { + type: "image", + uri: myPic, + alt: "this is a test image", + flipRTL: true + } + } and string ${"string"}}`) methodWithInlineImageAndOtherArguments(someNumber: number, image: "inline image", someString: string) { // NOTE: The `image` argument should not be used } diff --git a/extensions/documentation/src/porting/README.md b/extensions/documentation/src/porting/README.md index 9b9877fda..503c00f92 100644 --- a/extensions/documentation/src/porting/README.md +++ b/extensions/documentation/src/porting/README.md @@ -19,7 +19,7 @@ Here's how: 2. Create your "new" framework-based extension using the command outlined in [Making an Extension](https://github.com/mitmedialab/prg-extension-boilerplate/tree/main#-making-an-extension) and use the ***Extension ID*** you found in step 1 as the value of `` - For example, if your "old" extension's ***Extension ID*** is `prgRocks` you'll run the following command: ```bash - npm run new:extension prgRocks + pnpm new:extension prgRocks ``` - The reason this is necessary is two-fold: First, in the new extension framework, the name of the folder that contains an extension is automatically used as its ***Extension ID***. Second, because already saved `.sb3` / Scratch projects that use your extension refernce the specific ***Extension ID***, we need to make sure our updated, typescript-based extension has the same ID. 3. Once you have created an extension with a folder name matching the ***Extension ID*** found in step 1, you can actually delete the corresponding entry inside of the `builtinExtensions` object of [scratch-packages/scratch-vm/src/extension-support/extension-manager.js](https://github.com/mitmedialab/prg-extension-boilerplate/blob/main/scratch-packages/scratch-vm/src/extension-support/extension-manager.js) @@ -32,7 +32,7 @@ Here's how: - You can do this as the extension framework will automatically handle adding your extension (and its Extension Menu Display Details) to the [Extension Menu](https://en.scratch-wiki.info/wiki/Extension#Adding_Extensions) 6. Now you can start coding! See the below comparison of a vanilla JS extension class and a typescript / framework based one. - NOTE: If there's a chance anyone has saved projects with the extension you're porting over, you need to make sure to follow the [Legacy Support](#legacy-support) instructions so those saved projects will continue to load correctly. -7. Once you have migrated all of the "old" ***Impementation*** to your new extension folder & typescript code, you can go ahead and delete the ***Implementation*** folder inside of [pacakges/scratch-vm/src/extensions/](https://github.com/mitmedialab/prg-extension-boilerplate/tree/main/scratch-packages/scratch-vm/src/extensions). +7. Once you have migrated all of the "old" ***Impementation*** to your new extension folder & typescript code, you can go ahead and delete the ***Implementation*** folder inside of [scratch-packages/scratch-vm/src/extensions/](https://github.com/mitmedialab/prg-extension-boilerplate/tree/main/scratch-packages/scratch-vm/src/extensions). 8. Now, there should be no remnants of the "old" extension inside of either [scratch-packages/scratch-vm](https://github.com/mitmedialab/prg-extension-boilerplate/tree/main/scratch-packages/scratch-vm) or [scratch-packages/scratch-gui](https://github.com/mitmedialab/prg-extension-boilerplate/tree/main/scratch-packages/scratch-gui) folders, and instead everything lives neatly inside its own directory within [extensions/src](https://github.com/mitmedialab/prg-extension-boilerplate/tree/dev/extensions/src) 9. Test out the project you saved in step 0 to verify that your port worked as expected. diff --git a/extensions/documentation/src/porting/example.ts b/extensions/documentation/src/porting/example.ts index 5d03cdc2d..0a70de99f 100644 --- a/extensions/documentation/src/porting/example.ts +++ b/extensions/documentation/src/porting/example.ts @@ -1,5 +1,4 @@ -import { ArgumentType, BlockType, Environment, block, extension } from "$common"; -import BlockUtility from "$root/scratch-packages/scratch-vm/src/engine/block-utility"; +import { ArgumentType, BlockType, BlockUtilityWithID, Environment, extension, scratch } from "$common"; import formatMessage from './format-message'; // This should actually be an npm package and thus be 'format-message' const details = { @@ -13,22 +12,14 @@ export default class SomeBlocks extends extension(details) { init(env: Environment) { } - @block({ - type: BlockType.Reporter, - args: [ - { - type: ArgumentType.String, - defaultValue: 'text', - options: [ - { text: 'Item One', value: 'itemId1' }, - 'itemId2' - ] - }, - { type: ArgumentType.Number, defaultValue: 1 } - ], - text: (text, letterNum) => `letter ${letterNum} of ${text}'`, - }) - myReporter(text: string, letterNum: number, util: BlockUtility) { + @(scratch.reporter`letter ${ + { + type: "string", + defaultValue: 'text', + options: [{ text: 'Item One', value: 'itemId1' },'itemId2'] + } + } of ${{ type: "number", defaultValue: 1 }}`) + myReporter(text: string, letterNum: number, util: BlockUtilityWithID) { const message = formatMessage({ id: 'myReporter.result', default: 'Letter {letterNum} of {text} is {result}.', diff --git a/extensions/documentation/src/reference/README.md b/extensions/documentation/src/reference/README.md index b5809c2fc..604f7963e 100644 --- a/extensions/documentation/src/reference/README.md +++ b/extensions/documentation/src/reference/README.md @@ -8,7 +8,7 @@ sequenceDiagram participant B as /scratch-packages/scratch-gui/ participant C as /scratch-packages/scratch-vm/ Note over root,C: INITIALIZATION - C-->>B: Lerna utility forces scratch-gui
to use /scratch-packages/scratch-vm for its dependency
instead of using the external npm package + C-->>B: PNPM tells scratch-gui
to use /scratch-packages/scratch-vm for its dependency
instead of using the external npm package Note over root,C: BUILD Note over root: /scripts/build.ts activate root diff --git a/extensions/documentation/src/testing/index.ts b/extensions/documentation/src/testing/index.ts index 957e79a11..5f0e54904 100644 --- a/extensions/documentation/src/testing/index.ts +++ b/extensions/documentation/src/testing/index.ts @@ -3,7 +3,7 @@ import { codeSnippet } from "../../"; export const defineExtension = codeSnippet(); -import { block, buttonBlock, extension, Environment } from "$common"; +import { extension, Environment, scratch } from "$common"; const name = "Extension Under Test"; @@ -11,23 +11,15 @@ export default class ExtensionUnderTest extends extension({ name }, "ui") { init(env: Environment): void { } - @block({ - type: "command", - args: ["number", "number"], - text: (x, y) => "placeholder", - }) + @(scratch.command`placeholder: ${"number"} and ${"number"}`) exampleCommand(a: number, b: number) { /* Do something */ } - @block({ - type: "reporter", - text: (x) => "placeholder", - arg: "string", - }) + @(scratch.reporter`placeholder: ${"string"}`) exampleReporter(input: string) { return "Whatever you expect to be the output, given the input" } - @buttonBlock("placeholder") + @(scratch.button`placeholder`) exampleButtonThatOpensUI() { this.openUI("Test"); } diff --git a/extensions/documentation/src/tutorial/README.md b/extensions/documentation/src/tutorial/README.md index fd7e2961d..4545da159 100644 --- a/extensions/documentation/src/tutorial/README.md +++ b/extensions/documentation/src/tutorial/README.md @@ -5,7 +5,7 @@ Below is our starter piece of code, where we import the following items from the `$common` package: - `extension`: A factory function that returns a base class that our extension should [extend](https://www.typescriptlang.org/docs/handbook/2/classes.html#extends-clauses). This function allows for you to configure the capability of your Extension (as you'll see [later](#configuring-your-extensions-functionality)). -- `block`: A [method decorator](https://www.typescriptlang.org/docs/handbook/decorators.html#:~:text=Method%20Decorators,or%20replace%20a%20method%20definition.) function that will be [used below](#decorating-our-method-with-block) to mark our class method as something that should be turned into a Block. The argument provided to the `block` function contains all the information necessary to create a Block representation of our method in the Block Programming Environment. +- `scratch`: An object with two [method decorator](https://www.typescriptlang.org/docs/handbook/decorators.html#:~:text=Method%20Decorators,or%20replace%20a%20method%20definition.) functions -- `scratch.reporter`, `scratch.command`, `scratch.hat`, and `scratch.button` -- that will be [used below](#decorating-our-method-with-block) to mark our class method as something that should be turned into either a Reporter or Command Block. The [template literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) argument provided to the `scratch` functions contains all the information necessary to create a Block representation of our method in the Block Programming Environment. - `ExtensionMenuDisplayDetails`: A type imported to add typesafety to how we define the `details` object which we use when configuring our Extension's base class through the `extension` function. As the name suggests, the contents of this object determine how our Extension displays to the user, especially through the [Extensions Menu](https://en.scratch-wiki.info/wiki/Library#Extensions_Library). - `Environment`: A type imported to add typesafety to the argument of the `init` function (which all Extensions are required to implement -- it is used in place of a [constructor](https://www.typescriptlang.org/docs/handbook/2/classes.html#constructors)) @@ -29,17 +29,15 @@ Next, let's define a [method](https://www.typescriptlang.org/docs/handbook/2/cla [](./index.ts?export=method) -### Decorating our method with `block` +### Decorating our method with `scratch` -In order for this method to become a Block in the Block Programming Environment, we need to mark it (or [decorate](https://www.typescriptlang.org/docs/handbook/decorators.html#:~:text=Method%20Decorators,or%20replace%20a%20method%20definition.) it) with the `block` function. +In order for this method to become a Block in the Block Programming Environment, we need to mark it (or [decorate](https://www.typescriptlang.org/docs/handbook/decorators.html#:~:text=Method%20Decorators,or%20replace%20a%20method%20definition.) it) with the `scratch` functions (for example, `scratch.reporter`). [](./index.ts?export=blockify) -> **NOTE:** We preceed in the invocation of `block` with an `@` symbol which signifies the `block` function is [decorating](https://www.typescriptlang.org/docs/handbook/decorators.html#introduction) our method. +> **NOTE:** We preceed in the invocation of `scratch` with an `@` symbol which signifies the `scratch` function is [decorating](https://www.typescriptlang.org/docs/handbook/decorators.html#introduction) our method. -The argument that `block` takes provides all the information the Block Programming Environment needs in order to create a Block tied to your method. - -Your code editor and typescript will help ensure you provide all the necessary information, as well as give you a sense of the choices you have when defining this information. Make sure to hover over the fields of the object argument (i.g. `type`, `text`, `arg`, and/or `args`) to see thorough documentation on what those fields are and what values they can take on. +The template literal that the `scratch` decorator takes provides all the information the Block Programming Environment needs in order to create a Block tied to your method (i.g. the block's type, text, and args). #### Need to access properties of your Extension when define a Block?
@@ -47,13 +45,9 @@ Your code editor and typescript will help ensure you provide all the necessary i Open this -If you need to access some information on your extension when invoking `block`, you can do so by passing a function (instead of an object) as an argument. - -This function will accept one argument, which will be a reference to your Extension (the name `self` is used as a convention). You can then pull values off of it (like `defaultValue` below) when defining your return object. The returned object is the same type as the object argument of the `block` function. - -[](./index.ts?export=functionArg) +If you need to access some information on your extension when invoking `scratch` decorators, you can do so by passing a function (instead of an object) as an argument. -> **NOTE:** As you can see above, we need to make use of the `as const` [const assertion](https://dev.to/typescripttv/best-practices-with-const-assertions-in-typescript-4l10#:~:text=TypeScript%203.4%20introduced%20const%20assertions,pushed%20into%20an%20existing%20array.) when specifying the `args` field. This is only necessary because we are using a string value to define the `type` of our Arguments. Without `as const`, when typescript infers the return type of our function, it thinks our Arguments' `type`s are simply [strings](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean), which causes errors since a `string` is not a valid Argument type. By using `as const`, we ensure it infers our Argument types as [literals](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). +This function will accept two arguments: the first is a reference to your Extension, and the second is a tagged template literal you can use to define your block, just like the previous `scratch` functions. You can then pull values off of the Extension reference (like `defaultValue` below) in the placeholders of the template literal. [](./index.ts?export=functionArg) diff --git a/extensions/documentation/src/tutorial/complete.ts b/extensions/documentation/src/tutorial/complete.ts index 34ac1c2c7..9289c9a0f 100644 --- a/extensions/documentation/src/tutorial/complete.ts +++ b/extensions/documentation/src/tutorial/complete.ts @@ -1,4 +1,4 @@ -import { extension, block, type ExtensionMenuDisplayDetails, type Environment } from "$common"; +import { extension, type ExtensionMenuDisplayDetails, type Environment, scratch } from "$common"; const details: ExtensionMenuDisplayDetails = { name: "An Exciting Extension", @@ -11,11 +11,7 @@ const BaseClass = extension(details, "ui"); export default class Example extends BaseClass { init(env: Environment): void { } - @block({ - type: "command", - text: (name, age) => `What's your ${name} and ${age}?`, - args: [{ type: "string", defaultValue: "name" }, "number"] - }) + @(scratch.command`What's your ${{ type: "string", defaultValue: "name" }} and ${"number"}?`) someMethodToBlockify(name: string, age: number) { this.openUI("someComponent"); } diff --git a/extensions/documentation/src/tutorial/index.ts b/extensions/documentation/src/tutorial/index.ts index 8f3da355c..3f7567691 100644 --- a/extensions/documentation/src/tutorial/index.ts +++ b/extensions/documentation/src/tutorial/index.ts @@ -2,7 +2,7 @@ import { codeSnippet } from "documentation"; export const base = codeSnippet(); -import { extension, block, type ExtensionMenuDisplayDetails, type Environment } from "$common"; +import { extension, type ExtensionMenuDisplayDetails, type Environment, scratch } from "$common"; const details: ExtensionMenuDisplayDetails = { name: "" }; @@ -55,11 +55,7 @@ export const blockify = codeSnippet(); { class Example extends BaseClass { init(env: Environment): void { } - @block({ - type: "command", - text: (name, age) => `What's your ${name} and ${age}?`, - args: [{ type: "string", defaultValue: "name" }, "number"] - }) + @(scratch.command`What's your ${{ type: "string", defaultValue: "name" }} and ${"number"}?`) someMethodToBlockify(name: string, age: number) { console.log(`Hello, ${name}! ${age} is the new ${Math.random() * age * 2}`); } @@ -76,11 +72,7 @@ export const functionArg = codeSnippet(); { class Example extends _BaseClass { defaultValue = ""; - @block((self) => ({ - type: "command", - text: (name, age) => `What's your ${name} and ${age}?`, - args: [{ type: "string", defaultValue: self.defaultValue }, "number"] as const - })) + @(scratch.command((instance, $) => $`What's your ${{ type: "string", defaultValue: instance.defaultValue }} and ${"number"}?`)) someMethodToBlockify(name: string, age: number) { // ... functionArg.end; } @@ -96,11 +88,7 @@ export const ui = codeSnippet(); { class Example extends BaseClass { init(env: Environment): void { } - @block({ - type: "command", - text: (name, age) => `What's your ${name} and ${age}?`, - args: [{ type: "string", defaultValue: "name" }, "number"] - }) + @(scratch.command`What's your ${{ type: "string", defaultValue: "name" }} and ${"number"}?`) someMethodToBlockify(name: string, age: number) { this.openUI("someComponent"); } diff --git a/extensions/documentation/src/ui/README.md b/extensions/documentation/src/ui/README.md index d991705df..49773a515 100644 --- a/extensions/documentation/src/ui/README.md +++ b/extensions/documentation/src/ui/README.md @@ -10,7 +10,7 @@ Please first make sure you've satisfied [Svelte Dependency](https://github.com/m To generate a new svelte file, run the following command from the root of the project: ```bash -npm run add:ui +pnpm add:ui # For example: npm run add:ui myExtension ``` @@ -34,7 +34,7 @@ The first argument of the `openUI` method is the name of the `.svelte` file in w The second argument is the title that will display at the top of the modal window. If omitted, this will default to the name of your extension. -Below are two examples of declaring buttons (one using the standard `@block` decorator, and the other using the `@buttonBlock` decorator short-hand): +Here is an example of how to declare a button block: [](./index.ts?export=x) diff --git a/extensions/documentation/src/ui/index.ts b/extensions/documentation/src/ui/index.ts index ba256a5bb..2246a8826 100644 --- a/extensions/documentation/src/ui/index.ts +++ b/extensions/documentation/src/ui/index.ts @@ -4,23 +4,16 @@ const name = ""; export const x = codeSnippet(); -import { block, buttonBlock, extension } from "$common"; +import { extension, scratch } from "$common"; export default class ExampleExtension extends extension({ name }, "ui") { init = notRelevantToExample; - @block({ - type: "button", - text: `Button Text Goes Here` - }) - verboseButton() { + @(scratch.button`Button Text Goes Here`) + buttonBlock() { this.openUI("SvelteFileName", "Title of Window"); } - @buttonBlock(`Button Text Goes Here`) - shortHandButton() { - this.openUI("SvelteFileName", "Title of Window"); - } } x.end; \ No newline at end of file diff --git a/extensions/src/.templates/default.ts b/extensions/src/.templates/default.ts index 5ef20010a..8b6294c05 100644 --- a/extensions/src/.templates/default.ts +++ b/extensions/src/.templates/default.ts @@ -1,4 +1,4 @@ -import { ArgumentType, BlockType, Environment, ExtensionMenuDisplayDetails, extension, block } from "$common"; +import { ArgumentType, BlockType, BlockUtilityWithID, Environment, ExtensionMenuDisplayDetails, extension, scratch } from "$common"; import BlockUtility from "$scratch-vm/engine/block-utility"; /** 👋 Hi! @@ -43,34 +43,27 @@ export default class ExtensionNameGoesHere extends extension(details) { /** @see {ExplanationOfField} */ exampleField: number; + /** @see {ExplanationOfBlockType} */ /** @see {ExplanationOfReporterBlock} */ - @block({ type: "reporter", text: "This increments an internal field and then reports it's value" }) + @(scratch.reporter`This increments an internal field and then reports it's value`) exampleReporter() { return ++this.exampleField; } - + /** @see {ExplanationOfCommandBlock} */ - @block((self) => ({ - /** @see {ExplanationOfBlockType} */ - type: BlockType.Command, - /** @see {ExplanationOfBlockTextFunction} */ - text: (exampleString, exampleNumber) => `This is the block's display text with inputs here --> ${exampleString} and here --> ${exampleNumber}`, - /** @see {ExplanationOfBlockArgs} */ - args: [ArgumentType.String, { type: ArgumentType.Number, defaultValue: self.exampleField }], - })) + @(scratch.command( + (instance, $) => + /** @see {ExplanationOfBlockArg} */ + $`This is the block's display text with inputs here --> ${"string"} and here --> ${{type: "number", defaultValue: instance.exampleField}}` + )) exampleCommand(exampleString: string, exampleNumber: number) { alert(`This is a command! Here's what it received: ${exampleString} and ${exampleNumber}`); // Replace with what the block should do! } /** @see {ExplanationOfHatBlock} */ - /** @see {ExplanationOfBlockUtility} */ - @block({ - type: "hat", - text: (condition) => `Should the below block execute: ${condition}`, - /** @see {ExplanationOfBlockArg} */ - arg: "Boolean" - }) - async exampleHat(condition: boolean, util: BlockUtility) { + @(scratch.hat`Should the below block execute: ${"Boolean"}`) + /** @see {ExplanationOfBlockUtilityWithID} */ + async exampleHat(condition: boolean, util: BlockUtilityWithID) { return util.stackFrame.isLoop === condition; } } \ No newline at end of file diff --git a/extensions/src/appinventor_example/index.ts b/extensions/src/appinventor_example/index.ts index 38db52e19..a9b2ee1d2 100644 --- a/extensions/src/appinventor_example/index.ts +++ b/extensions/src/appinventor_example/index.ts @@ -1,4 +1,4 @@ -import { Environment, extension, block, getterBlock, PropertyBlockDetails, setterBlock, Matrix } from "$common"; +import { Environment, extension, getterBlock, PropertyBlockDetails, setterBlock, Matrix, scratch } from "$common"; const heightProperty: PropertyBlockDetails = { name: "Height", type: "number" }; @@ -18,11 +18,7 @@ export default class extends extension({ name: "App Inventor Example", tags: ["P this.field = value; } - @block({ - text: (x, y, z) => `${x} ${y} ${z}`, - args: ["number", "string", "matrix"], - type: "reporter" - }) + @(scratch.reporter`${"number"} ${"string"} ${"matrix"}`) dummy(x: number, y: string, z: Matrix): number { return 0; } diff --git a/extensions/src/common/extension/decorators/taggedTemplate.ts b/extensions/src/common/extension/decorators/taggedTemplate.ts index 7d896a44f..748e323fc 100644 --- a/extensions/src/common/extension/decorators/taggedTemplate.ts +++ b/extensions/src/common/extension/decorators/taggedTemplate.ts @@ -83,4 +83,5 @@ export const scratch = { reporter: makeDecorator("reporter"), command: makeDecorator("command"), button: makeDecorator("button"), + hat: makeDecorator("hat") } \ No newline at end of file diff --git a/extensions/src/common/templateDocs.ts b/extensions/src/common/templateDocs.ts index a4284d250..5aad4c213 100644 --- a/extensions/src/common/templateDocs.ts +++ b/extensions/src/common/templateDocs.ts @@ -25,23 +25,19 @@ type Extension = Documentation; * - [string](https://www.typescriptlang.org/docs/handbook/basic-types.html#string), [number](https://www.typescriptlang.org/docs/handbook/basic-types.html#number), or [boolean](https://www.typescriptlang.org/docs/handbook/basic-types.html#boolean) * - If none of the above types fit your needs, please contact a more experienced developer who can walk you through things like [Custom Arguments](https://github.com/mitmedialab/prg-extension-boilerplate/tree/dev/extensions#adding-custom-arguments) * 2. Once you have your method (_what the Block does_), you'll need to add some extra info to it to tell the Block Programming Environment how to present it to the user. - * - This requires you to [decorate](https://www.typescriptlang.org/docs/handbook/decorators.html#method-decorators) your method with the `block` function, - * which we do by putting an '@' symbol in front of `block` and invoking it. - * - The `block` function accepts one argument which provides all the information + * - This requires you to [decorate](https://www.typescriptlang.org/docs/handbook/decorators.html#method-decorators) your method with a `scratch` function, + * which we do by putting an '@' symbol in front of `scratch` and invoking either `scratch.command`, `scratch.reporter`, `scratch.button`, or `scratch.hat`. + * - Using a template literal, the `scratch` function provides all the information * the Block Programming Environment will need, like: - * - `type`: What type of block is it / how should it be used? - * - `text`: What text is displayed on the block? - * - `arg` or `args`: How should the method's arguments map to input fields on the block? + * - type: What type of block is it / how should it be used? + * - text: What text is displayed on the block? + * - args: How should the method's arguments map to input fields on the block? * - See [Scratch's documentation](https://github.com/LLK/scratch-vm/blob/develop/docs/extensions.md#block-arguments) to get a sense of how your method's argument types map to the input fields the Block Programming Environment supports * * @example * **NOTE:** Ignore the `.` in front of the `@` symbol -- this is just so JSDoc doesn't mess with the formatting. * ```ts - * .@block({ - * type: "command", - * text: "This is the text that will display on the block", - * args: ["string", "note"] - * }) + * .@(scratch.command`This is the text that will display on the block`) * exampleMethod(someText: string, noteValue: number) { * ... * } @@ -107,57 +103,45 @@ type ExplanationOfInitMethod = Documentation; * Hover over `exampleReporter` below to observe it's type signature and that it returns a `number` * (which is _inferred_ by Typescript, but you could also be [explicitly stated](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#return-type-annotations)). * - * As described in the definiton of Block above (hover over it if you need a refresher), we turn the `exampleReporter` method into a method tied to a Block by "decorating" it with the `block` decorator function + * As described in the definiton of Block above (hover over it if you need a refresher), we turn the `exampleReporter` method into a method tied to a Block by "decorating" it with the `scratch.reporter` decorator function * (the use of the `@` tell us that it is a [decorator](https://www.typescriptlang.org/docs/handbook/decorators.html#method-decorators)). * - * As mentioned above, the `block` function takes a single argument as input, which provides all the necessary information - * for the Block Programming Environment to create a Block tied to our method. + * In this scenario, `scratch.reporter` serves as a [Tagged Template Literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). This means we can use the passed-in [Template String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) to provide + * all the necessary information for the Block Programming Environment to create a Reporter Block + * tied to our method, with input fields at the positions of the templated arguments. * - * In this case, all we need is the block type (`type`) and the display text of the block (`text`). */ type ExplanationOfReporterBlock = Documentation; /** - * Below, we define a method `exampleCommand` and decorate it with the `@block` function (similiar to above). + * Below, we define a method `exampleCommand` and decorate it with the `@scratch.command` function (similiar to above). * This is an example of a "command" block, as the underlying method takes 0 or more arguments, and returns nothing ([void](https://www.typescriptlang.org/docs/handbook/basic-types.html#void)). * * Hover over `exampleCommand` below to observe it's type signature and how it returns `void`. * - * **NOTE:** Here, instead of passing an object to the `@block` decorator function (as above), we pass another function to it. - * This function must take a single parameter, which will be a reference to our specific Extension (hover over `self` if you don't believe me). + * **NOTE:** Here, instead of using a tagged template literal (as above), we pass another function to the `scratch` decorator. + * This function takes two parameters, the first of which is a reference to our specific Extension. * As you can see in the definition of the `defaultValue` of our second argument, * this allows us to pull values off of our extension when defining our block. + * The second argument references the tagged template literal we use to create the text and arguments of the block. */ type ExplanationOfCommandBlock = Documentation; /** - * We can either use the string to define our blockType, like above, - * or refernce a specific entry on the `BlockType` object, like below. - * - * Make sure to hover over the `type` field to get more comprehensive documentation. + * We can use the `scratch` decorator functions to define four types of blocks: a command block, + * a reporter block, a button block, or a hat block. */ type ExplanationOfBlockType = Documentation; /** - * Because the underlying `exampleCommand` method takes arguments, - * our `text` field must implement a function which accepts the same arguments as the method - * (instead of just providing a `string` as is done above). - * - * In the implementation of this function, we should create a [Template String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) that references our arguments, + * Because the underlying `exampleCommand` method takes arguments, we use a [Template String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) with placeholders that match the arguments of the method, * which will auto-magically cause the resulting Blocks to have input fields at the positions of the templated arguments. - * - * Make sure to hover over the `text` field to get more comprehensive documentation. */ type ExplanationOfBlockTextFunction = Documentation; /** - * Because our method accepts 2 arguments, we must define an `args` field - * (necessary whenever your Block's method accepts 2 or more arguments). - * - * This will accept an array of _Arguments_ that correspond to the types of the method's arguments. - * - * As you can see below, how you define _Arguments_ can vary, so make sure to hover over the `args` field to get more comprehensive documentation - * and leverage the examples to understand the different forms in which you can define _Arguments_. + * Because our method accepts two arguments, we must define a template literal with two placeholders. + * Each placeholder will correspond to the types of the method's arguments. */ type ExplanationOfBlockArgs = Documentation; @@ -182,16 +166,16 @@ type ExplanationOfHatBlock = Documentation; type ExplanationOfBlockArg = Documentation; /** - * You should notice that the final argument of our method is of type `BlockUtility`. + * You should notice that the final argument of our method is of type `BlockUtilityWithID`. * - * For every Block method, you can optionally add a final parameter of type `BlockUtility` + * For every Block method, you can optionally add a final parameter of type `BlockUtilityWithID` * which can be used to accomplish more [advanced behaviours](https://github.com/mitmedialab/prg-extension-boilerplate/tree/dev/extensions#making-use-of-the-block-utility). * No need to worry too much about when you're first starting out, though! * - * As is the case here, note that the inclusion of a `BlockUtility` argument does not "count" + * As is the case here, note that the inclusion of a `BlockUtilityWithID` argument does not "count" * as an Argument for your Block. * - * Thus, if your method **_only_** accepts a `BlockUtility` argument, then the `block` function will **not** - * require (or allow) you to define an `arg` nor `args` field -- similiar to the `exampleReporter` method / Block above. + * Thus, if your method **_only_** accepts a `BlockUtilityWithID` argument, then the `block` function will **not** + * require (or allow) you to add a placeholder in the template literal -- similiar to the `exampleReporter` method / Block above. */ -type ExplanationOfBlockUtility = Documentation; \ No newline at end of file +type ExplanationOfBlockUtilityWithID = Documentation; \ No newline at end of file