From 8a08c1f398b7b5ed80b5943192ba8e8edee49af5 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Fri, 5 Apr 2024 18:57:29 +0000 Subject: [PATCH 1/5] feat: move backpack to use interface for backpackability --- plugins/workspace-backpack/src/backpack.ts | 47 ++++++++++++++++--- .../workspace-backpack/src/backpackable.ts | 24 ++++++++++ 2 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 plugins/workspace-backpack/src/backpackable.ts diff --git a/plugins/workspace-backpack/src/backpack.ts b/plugins/workspace-backpack/src/backpack.ts index f7ba4c6c1a..d831737eba 100644 --- a/plugins/workspace-backpack/src/backpack.ts +++ b/plugins/workspace-backpack/src/backpack.ts @@ -16,6 +16,7 @@ import * as Blockly from 'blockly/core'; import {registerContextMenus} from './backpack_helpers'; import {BackpackOptions, parseOptions} from './options'; import {BackpackChange, BackpackOpen} from './ui_events'; +import {Backpackable, isBackpackable} from './backpackable'; /** * Class for backpack that can be used save blocks from the workspace for @@ -453,8 +454,8 @@ export class Backpack * being dragged. */ onDrop(dragElement: Blockly.IDraggable) { - if (dragElement instanceof Blockly.BlockSvg) { - this.addBlock(dragElement); + if (isBackpackable(dragElement)) { + this.addBackpackable(dragElement); } } @@ -525,7 +526,7 @@ export class Backpack * provided block. */ containsBlock(block: Blockly.Block): boolean { - return this.contents_.indexOf(this.blockToJsonString(block)) !== -1; + return isBackpackable(block) && this.containsBackpackable(block); } /** @@ -534,7 +535,7 @@ export class Backpack * @param block The block to be added to the backpack. */ addBlock(block: Blockly.Block) { - this.addItem(this.blockToJsonString(block)); + if (isBackpackable(block)) this.addBackpackable(block); } /** @@ -544,7 +545,9 @@ export class Backpack * backpack. */ addBlocks(blocks: Blockly.Block[]) { - this.addItems(blocks.map(this.blockToJsonString)); + for (const block of blocks) { + if (isBackpackable(block)) this.addBackpackable(block); + } } /** @@ -553,7 +556,39 @@ export class Backpack * @param block The block to be removed from the backpack. */ removeBlock(block: Blockly.Block) { - this.removeItem(this.blockToJsonString(block)); + if (isBackpackable(block)) this.removeBackpackable(block); + } + + /** + * Returns whether the backpack contains a duplicate of the provided + * backpackable. + */ + containsBackpackable(backpackable: Backpackable) { + return backpackable + .toFlyoutData() + .every((info) => this.contents_.indexOf(JSON.stringify(info)) !== -1); + } + + /** Adds the given backpackable to the backpack. */ + addBackpackable(backpackable: Backpackable) { + this.addBackpackables([backpackable]); + } + + /** Adds the given backpackables to the backpack. */ + addBackpackables(backpackables: Backpackable[]) { + this.addItems( + backpackables + .map((b) => b.toFlyoutData()) + .reduce((acc, curr) => [...acc, ...curr]) + .map((info) => JSON.stringify(info)), + ); + } + + /** Removes the given backpackable from the backpack, if it exists. */ + removeBackpackable(backpackable: Backpackable) { + for (const info of backpackable.toFlyoutData()) { + this.removeItem(JSON.stringify(info)); + } } /** diff --git a/plugins/workspace-backpack/src/backpackable.ts b/plugins/workspace-backpack/src/backpackable.ts new file mode 100644 index 0000000000..c9ef472459 --- /dev/null +++ b/plugins/workspace-backpack/src/backpackable.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2024 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as Blockly from 'blockly/core'; + +/** Defines if an object can be added to the backpack. */ +export interface Backpackable { + /** + * Returns a representation of this object as a FlyoutItemInfo array, so that + * it can be displayed in the backpack. + * + * This method should remove any unique or useless state data (e.g. IDs or + * coordinates). + */ + toFlyoutData(): Blockly.utils.toolbox.FlyoutItemInfo[]; +} + +/** Checks whether the given object conforms to the Backpackable interface. */ +export function isBackpackable(obj: any): obj is Backpackable { + return obj.toFlyoutData !== undefined; +} From 351fa186a122e9a8b72042eeaf8ab566faa46e03 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Mon, 8 Apr 2024 17:21:30 +0000 Subject: [PATCH 2/5] chore: PR comments --- plugins/workspace-backpack/src/backpack.ts | 44 ++++++++++++++----- .../workspace-backpack/src/backpackable.ts | 12 +++-- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/plugins/workspace-backpack/src/backpack.ts b/plugins/workspace-backpack/src/backpack.ts index d831737eba..6ba28269dd 100644 --- a/plugins/workspace-backpack/src/backpack.ts +++ b/plugins/workspace-backpack/src/backpack.ts @@ -526,7 +526,11 @@ export class Backpack * provided block. */ containsBlock(block: Blockly.Block): boolean { - return isBackpackable(block) && this.containsBackpackable(block); + if (isBackpackable(block)) { + return this.containsBackpackable(block); + } else { + return this.contents_.indexOf(this.blockToJsonString(block)) !== -1; + } } /** @@ -535,7 +539,11 @@ export class Backpack * @param block The block to be added to the backpack. */ addBlock(block: Blockly.Block) { - if (isBackpackable(block)) this.addBackpackable(block); + if (isBackpackable(block)) { + this.addBackpackable(block); + } else { + this.addItem(this.blockToJsonString(block)); + } } /** @@ -546,7 +554,11 @@ export class Backpack */ addBlocks(blocks: Blockly.Block[]) { for (const block of blocks) { - if (isBackpackable(block)) this.addBackpackable(block); + if (isBackpackable(block)) { + this.addBackpackable(block); + } else { + this.addItem(this.blockToJsonString(block)); + } } } @@ -556,37 +568,45 @@ export class Backpack * @param block The block to be removed from the backpack. */ removeBlock(block: Blockly.Block) { - if (isBackpackable(block)) this.removeBackpackable(block); + if (isBackpackable(block)) { + this.removeBackpackable(block); + } else { + this.removeItem(this.blockToJsonString(block)); + } } /** - * Returns whether the backpack contains a duplicate of the provided - * backpackable. + * @param backpackable The backpackable we want to check for existance within + * the backpack. + * @return whether the backpack contains a duplicate of the provided + * backpackable. */ containsBackpackable(backpackable: Backpackable) { return backpackable - .toFlyoutData() + .toFlyoutInfo() .every((info) => this.contents_.indexOf(JSON.stringify(info)) !== -1); } - /** Adds the given backpackable to the backpack. */ + /** + * @param backpackable The backpackable to add to the backpack. + */ addBackpackable(backpackable: Backpackable) { this.addBackpackables([backpackable]); } - /** Adds the given backpackables to the backpack. */ + /** @param backpackables The backpackables to add to the backpack. */ addBackpackables(backpackables: Backpackable[]) { this.addItems( backpackables - .map((b) => b.toFlyoutData()) + .map((b) => b.toFlyoutInfo()) .reduce((acc, curr) => [...acc, ...curr]) .map((info) => JSON.stringify(info)), ); } - /** Removes the given backpackable from the backpack, if it exists. */ + /** @param backpackable The backpackable to remove from the backpack. */ removeBackpackable(backpackable: Backpackable) { - for (const info of backpackable.toFlyoutData()) { + for (const info of backpackable.toFlyoutInfo()) { this.removeItem(JSON.stringify(info)); } } diff --git a/plugins/workspace-backpack/src/backpackable.ts b/plugins/workspace-backpack/src/backpackable.ts index c9ef472459..6b75f96da1 100644 --- a/plugins/workspace-backpack/src/backpackable.ts +++ b/plugins/workspace-backpack/src/backpackable.ts @@ -9,16 +9,20 @@ import * as Blockly from 'blockly/core'; /** Defines if an object can be added to the backpack. */ export interface Backpackable { /** - * Returns a representation of this object as a FlyoutItemInfo array, so that + * @return A representation of this object as a FlyoutItemInfo array, so that * it can be displayed in the backpack. * * This method should remove any unique or useless state data (e.g. IDs or * coordinates). */ - toFlyoutData(): Blockly.utils.toolbox.FlyoutItemInfo[]; + toFlyoutInfo(): Blockly.utils.toolbox.FlyoutItemInfo[]; } -/** Checks whether the given object conforms to the Backpackable interface. */ +/** + * @param obj The object we want to check is a Backpackable or not. + * @return Whether the given object conforms to the Backpackable interface. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function isBackpackable(obj: any): obj is Backpackable { - return obj.toFlyoutData !== undefined; + return obj.toFlyoutInfo !== undefined; } From 81fa5cb39d72e977b0c27744b9c42a225e21194a Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Mon, 8 Apr 2024 20:19:14 +0000 Subject: [PATCH 3/5] fix: nits --- plugins/workspace-backpack/src/backpack.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/workspace-backpack/src/backpack.ts b/plugins/workspace-backpack/src/backpack.ts index 6ba28269dd..70740efa9c 100644 --- a/plugins/workspace-backpack/src/backpack.ts +++ b/plugins/workspace-backpack/src/backpack.ts @@ -576,12 +576,12 @@ export class Backpack } /** - * @param backpackable The backpackable we want to check for existance within + * @param backpackable The backpackable we want to check for existence within * the backpack. * @return whether the backpack contains a duplicate of the provided * backpackable. */ - containsBackpackable(backpackable: Backpackable) { + containsBackpackable(backpackable: Backpackable): boolean { return backpackable .toFlyoutInfo() .every((info) => this.contents_.indexOf(JSON.stringify(info)) !== -1); From d227d74670d06005bcde71f4b6130300989f6f9a Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Mon, 8 Apr 2024 20:34:16 +0000 Subject: [PATCH 4/5] chore: update README --- plugins/workspace-backpack/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/plugins/workspace-backpack/README.md b/plugins/workspace-backpack/README.md index 88559e8bf5..c1288012eb 100644 --- a/plugins/workspace-backpack/README.md +++ b/plugins/workspace-backpack/README.md @@ -153,6 +153,11 @@ Blockly.Msg['EMPTY_BACKPACK'] = 'Opróżnij plecak'; // Polish - `addBlock`: Adds Block to backpack. - `addBlocks`: Adds Blocks to backpack. - `removeBlock`: Removes Block to backpack. +- `containsBlock`: Returns whether the Block is in the backpack. +- `addBackpackable`: Adds Backpackable to backpack. +- `addBackpackables`: Adds Backpackable to backpack. +- `removeBackpackable`: Removes Backpackable to backpack. +- `containsBackpackable`: Returns whether the Backpackable is in the backpack. - `addItem`: Adds item to backpack. - `removeItem`: Removes item from the backpack. - `setContents`: Sets backpack contents. @@ -201,6 +206,17 @@ The Backpack Flyout uses the registered Flyout for either for `Blockly.Trashcan`. If a custom class is registered for either of these types, then the Backpack Flyout may need to be tested for compatibility. +### Draggables + +If you have a custom draggable object, you can make it possible to add it to +the backpack by implementing the [`Backpackable`][backpackable] interface. + +This interface requires the draggable to have a method that converts it into +[`FlyoutItemInfo`][flyout-info]. As of 2024-04-08 flyouts only support displaying blocks, buttons, and labels. + ## License Apache 2.0 + +[flyout-info]: https://developers.google.com/blockly/reference/js/blockly.utils_namespace.toolbox_namespace.flyoutiteminfo_typealias.md +[backpackable]: https://github.com/google/blockly-samples/blob/master/plugins/workspace-backpack/src/backpack.ts From a11c8613f68bc4e6e80ffcde21bc5d2d6795b8a0 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Tue, 9 Apr 2024 18:01:23 +0000 Subject: [PATCH 5/5] chore: use version number --- plugins/workspace-backpack/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/workspace-backpack/README.md b/plugins/workspace-backpack/README.md index c1288012eb..274f5999ae 100644 --- a/plugins/workspace-backpack/README.md +++ b/plugins/workspace-backpack/README.md @@ -212,7 +212,7 @@ If you have a custom draggable object, you can make it possible to add it to the backpack by implementing the [`Backpackable`][backpackable] interface. This interface requires the draggable to have a method that converts it into -[`FlyoutItemInfo`][flyout-info]. As of 2024-04-08 flyouts only support displaying blocks, buttons, and labels. +[`FlyoutItemInfo`][flyout-info]. As of Blockly core v11 flyouts only support displaying blocks, buttons, and labels. ## License