scratch-blocks 2.0.0-spork.3 → 2.0.0-spork.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,15 +1,3 @@
1
- /**
2
- * @license
3
- * Copyright 2020 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
-
7
- /**
8
- * @license
9
- * Copyright 2021 Google LLC
10
- * SPDX-License-Identifier: Apache-2.0
11
- */
12
-
13
1
  /**
14
2
  * @license
15
3
  * Copyright 2023 Google LLC
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scratch-blocks",
3
- "version": "2.0.0-spork.3",
3
+ "version": "2.0.0-spork.4",
4
4
  "description": "Scratch Blocks is a library for building creative computing interfaces.",
5
5
  "author": "Massachusetts Institute of Technology",
6
6
  "license": "Apache-2.0",
@@ -32,9 +32,9 @@
32
32
  "webpack-dev-server": "^4.11.1"
33
33
  },
34
34
  "dependencies": {
35
- "@blockly/continuous-toolbox": "^6.0.9",
36
- "@blockly/field-colour": "^5.0.9",
37
- "blockly": "^12.0.0-beta.0"
35
+ "@blockly/continuous-toolbox": "^7.0.0-beta.1",
36
+ "@blockly/field-colour": "^4.0.2",
37
+ "blockly": "^12.0.0-beta.1"
38
38
  },
39
39
  "config": {
40
40
  "commitizen": {
@@ -0,0 +1,112 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2024 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import * as Blockly from "blockly/core";
8
+ import { ContinuousFlyout } from "@blockly/continuous-toolbox";
9
+ import { CheckboxBubble } from "./checkbox_bubble";
10
+ import { StatusIndicatorLabel } from "./status_indicator_label";
11
+ import { STATUS_INDICATOR_LABEL_TYPE } from "./status_indicator_label_flyout_inflater";
12
+
13
+ export class CheckableContinuousFlyout extends ContinuousFlyout {
14
+ /**
15
+ * Creates a new CheckableContinuousFlyout.
16
+ *
17
+ * @param workspaceOptions Configuration options for the flyout workspace.
18
+ */
19
+ constructor(workspaceOptions: Blockly.Options) {
20
+ workspaceOptions.modalInputs = false;
21
+ super(workspaceOptions);
22
+ this.tabWidth_ = 0;
23
+ this.MARGIN = 12;
24
+ this.GAP_Y = 12;
25
+ }
26
+
27
+ /**
28
+ * Serializes a block to JSON in order to copy it to the main workspace.
29
+ *
30
+ * @param block The block to serialize.
31
+ * @returns A JSON representation of the block.
32
+ */
33
+ protected serializeBlock(block: Blockly.BlockSvg) {
34
+ const json = super.serializeBlock(block);
35
+ // Delete the serialized block's ID so that a new one is generated when it is
36
+ // placed on the workspace. Otherwise, the block on the workspace may be
37
+ // indistinguishable from the one in the flyout, which can cause reporter blocks
38
+ // to have their value dropdown shown in the wrong place.
39
+ delete json.id;
40
+ return json;
41
+ }
42
+
43
+ /**
44
+ * Set the state of a checkbox by block ID.
45
+ *
46
+ * @param blockId ID of the block whose checkbox should be set
47
+ * @param value Value to set the checkbox to.
48
+ */
49
+ setCheckboxState(blockId: string, value: boolean) {
50
+ this.getWorkspace()
51
+ .getBlockById(blockId)
52
+ ?.getIcon("checkbox")
53
+ ?.setChecked(value);
54
+ }
55
+
56
+ getFlyoutScale() {
57
+ return 0.675;
58
+ }
59
+
60
+ getWidth() {
61
+ return 250;
62
+ }
63
+
64
+ protected reflowInternal_() {
65
+ super.reflowInternal_();
66
+
67
+ if (this.RTL) {
68
+ // The parent implementation assumes that the flyout grows to fit its
69
+ // contents, and adjusts blocks in RTL mode accordingly. In Scratch, the
70
+ // flyout width is fixed (and blocks may exceed it), so re-adjust blocks
71
+ // accordingly based on the actual fixed width.
72
+ for (const item of this.getContents()) {
73
+ const oldX = item.getElement().getBoundingRectangle().left;
74
+ let newX =
75
+ this.getWidth() / this.workspace_.scale -
76
+ item.getElement().getBoundingRectangle().getWidth() -
77
+ this.MARGIN;
78
+ if (
79
+ "checkboxInFlyout" in item.getElement() &&
80
+ item.getElement().checkboxInFlyout
81
+ ) {
82
+ newX -= CheckboxBubble.CHECKBOX_SIZE + CheckboxBubble.CHECKBOX_MARGIN;
83
+ }
84
+ item.getElement().moveBy(newX - oldX, 0);
85
+ }
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Validates that the given toolbox item represents a label.
91
+ *
92
+ * @param item The toolbox item to check.
93
+ * @returns True if the item represents a label in the flyout.
94
+ */
95
+ protected toolboxItemIsLabel(item: Blockly.FlyoutItem) {
96
+ return (
97
+ item.getType() === STATUS_INDICATOR_LABEL_TYPE ||
98
+ super.toolboxItemIsLabel(item)
99
+ );
100
+ }
101
+
102
+ /**
103
+ * Updates the state of status indicators for hardware-based extensions.
104
+ */
105
+ refreshStatusButtons() {
106
+ for (const item of this.contents) {
107
+ if (item.element instanceof StatusIndicatorLabel) {
108
+ item.element.refreshStatus();
109
+ }
110
+ }
111
+ }
112
+ }
@@ -203,15 +203,14 @@ export class CheckboxBubble
203
203
  * Recalculates this bubble's location, keeping it adjacent to its block.
204
204
  */
205
205
  updateLocation() {
206
- const blockLocation = this.sourceBlock.getRelativeToSurfaceXY();
207
- const blockBounds = this.sourceBlock.getHeightWidth();
206
+ const bounds = this.sourceBlock.getBoundingRectangle();
208
207
  const x = this.sourceBlock.workspace.RTL
209
- ? blockLocation.x + blockBounds.width + CheckboxBubble.CHECKBOX_MARGIN
210
- : blockLocation.x -
208
+ ? bounds.right + CheckboxBubble.CHECKBOX_MARGIN
209
+ : bounds.left -
211
210
  CheckboxBubble.CHECKBOX_MARGIN -
212
211
  CheckboxBubble.CHECKBOX_SIZE;
213
212
  const y =
214
- blockLocation.y + (blockBounds.height - CheckboxBubble.CHECKBOX_SIZE) / 2;
213
+ bounds.top + (bounds.getHeight() - CheckboxBubble.CHECKBOX_SIZE) / 2;
215
214
  this.moveTo(x, y);
216
215
  }
217
216
 
package/src/css.ts CHANGED
@@ -751,8 +751,9 @@ const styles = `
751
751
  }
752
752
 
753
753
  /* Category tree in Toolbox. */
754
- .blocklyToolboxDiv {
754
+ .blocklyToolbox {
755
755
  background-color: var(--colour-toolbox);
756
+ border-right: 1px solid #ddd;
756
757
  color: var(--colour-toolboxText);
757
758
  overflow-x: visible;
758
759
  overflow-y: auto;
@@ -763,6 +764,11 @@ const styles = `
763
764
  padding: 0;
764
765
  }
765
766
 
767
+ .blocklyToolbox[dir="RTL"] {
768
+ border-right: none;
769
+ border-left: 1px solid #ddd;
770
+ }
771
+
766
772
  .blocklyTreeRoot {
767
773
  padding: 4px 0;
768
774
  }
@@ -771,7 +777,7 @@ const styles = `
771
777
  outline: none;
772
778
  }
773
779
 
774
- .blocklyToolboxDiv .blocklyTreeRow {
780
+ .blocklyToolbox .blocklyToolboxCategory {
775
781
  line-height: 22px;
776
782
  margin: 0;
777
783
  padding: 0.375rem 0px;
@@ -789,11 +795,11 @@ const styles = `
789
795
  margin: 1px 0 8px 5px;
790
796
  }
791
797
 
792
- .blocklyToolboxDiv[dir="RTL"] .blocklyTreeRow {
793
- margin-left: 8px;
798
+ .blocklyToolbox[dir="RTL"] .blocklyToolboxCategory {
799
+ margin-left: 0px;
794
800
  }
795
801
 
796
- .blocklyTreeRow:hover {
802
+ .blocklyToolboxCategory:hover {
797
803
  color: var(--colour-toolboxHover);
798
804
  }
799
805
 
@@ -844,7 +850,7 @@ const styles = `
844
850
  background-position: -48px -1px;
845
851
  }
846
852
 
847
- .blocklyTreeLabel {
853
+ .blocklyToolboxCategoryLabel {
848
854
  cursor: default;
849
855
  font-family: "Helvetica Neue", Helvetica, sans-serif;
850
856
  font-size: .65rem;
@@ -855,7 +861,7 @@ const styles = `
855
861
  text-wrap: wrap;
856
862
  }
857
863
 
858
- .blocklyTreeSelected .blocklyTreeLabel {
864
+ .blocklyToolboxSelected .blocklyToolboxCategoryLabel {
859
865
  color: inherit;
860
866
  }
861
867
 
package/src/index.ts CHANGED
@@ -26,11 +26,10 @@ import "./css";
26
26
  import "./renderer/renderer";
27
27
  import * as contextMenuItems from "./context_menu_items";
28
28
  import {
29
- ContinuousToolbox,
30
- ContinuousFlyout,
29
+ registerContinuousToolbox,
31
30
  ContinuousMetrics,
32
31
  } from "@blockly/continuous-toolbox";
33
- import { CheckableContinuousFlyout } from "./checkable_continuous_flyout.js";
32
+ import { CheckableContinuousFlyout } from "./checkable_continuous_flyout";
34
33
  import { buildGlowFilter, glowStack } from "./glows";
35
34
  import { ScratchContinuousToolbox } from "./scratch_continuous_toolbox";
36
35
  import "./scratch_comment_icon";
@@ -120,6 +119,7 @@ export function inject(container: Element, options: Blockly.BlocklyOptions) {
120
119
  return workspace;
121
120
  }
122
121
 
122
+ registerContinuousToolbox();
123
123
  Blockly.Scrollbar.scrollbarThickness = Blockly.Touch.TOUCH_ENABLED ? 14 : 11;
124
124
  Blockly.FlyoutButton.TEXT_MARGIN_X = 40;
125
125
  Blockly.FlyoutButton.TEXT_MARGIN_Y = 10;
@@ -6,21 +6,12 @@
6
6
 
7
7
  import * as Blockly from "blockly/core";
8
8
  import { CheckboxBubble } from "./checkbox_bubble";
9
+ import { RecyclableBlockFlyoutInflater as BlocklyRecyclableBlockFlyoutInflater } from "@blockly/continuous-toolbox";
9
10
 
10
11
  /**
11
12
  * A block inflater that caches and reuses blocks to improve performance.
12
13
  */
13
- export class RecyclableBlockFlyoutInflater extends Blockly.BlockFlyoutInflater {
14
- /**
15
- * Whether or not block recycling is enabled.
16
- */
17
- recyclingEnabled = true;
18
-
19
- /**
20
- * Map from block type to block instance.
21
- */
22
- recycledBlocks = new Map<string, Blockly.BlockSvg>();
23
-
14
+ export class RecyclableBlockFlyoutInflater extends BlocklyRecyclableBlockFlyoutInflater {
24
15
  /**
25
16
  * Creates a block on the flyout workspace from the given block definition.
26
17
  *
@@ -31,152 +22,18 @@ export class RecyclableBlockFlyoutInflater extends Blockly.BlockFlyoutInflater {
31
22
  load(
32
23
  state: Blockly.utils.toolbox.BlockInfo,
33
24
  flyoutWorkspace: Blockly.WorkspaceSvg
34
- ): Blockly.IBoundedElement {
35
- const block = super.load(state, flyoutWorkspace);
25
+ ): Blockly.FlyoutItem {
26
+ const flyoutItem = super.load(state, flyoutWorkspace);
27
+ const block = flyoutItem.getElement();
36
28
  if ("checkboxInFlyout" in block && block.checkboxInFlyout) {
37
29
  block.moveBy(
38
- CheckboxBubble.CHECKBOX_SIZE + CheckboxBubble.CHECKBOX_MARGIN,
30
+ (flyoutWorkspace.RTL ? -1 : 1) *
31
+ (CheckboxBubble.CHECKBOX_SIZE + CheckboxBubble.CHECKBOX_MARGIN),
39
32
  0
40
33
  );
41
34
  }
42
35
 
43
- return block;
44
- }
45
-
46
- /**
47
- * Toggles whether or not recycling is enabled.
48
- *
49
- * @param enabled True if recycling should be enabled.
50
- */
51
- setRecyclingEnabled(enabled: boolean) {
52
- this.recyclingEnabled = enabled;
53
- }
54
-
55
- /**
56
- * Creates a new block from the given block definition.
57
- *
58
- * @param blockDefinition The definition to create a block from.
59
- * @returns The newly created block.
60
- */
61
- createBlock(
62
- blockDefinition: Blockly.utils.toolbox.BlockInfo
63
- ): Blockly.BlockSvg {
64
- const blockType = this.getTypeFromDefinition(blockDefinition);
65
- return (
66
- this.getRecycledBlock(blockType) ??
67
- super.createBlock(blockDefinition, this.flyoutWorkspace)
68
- );
69
- }
70
-
71
- /**
72
- * Returns the type of a block from an XML or JSON block definition.
73
- *
74
- * @param blockDefinition The block definition to parse.
75
- * @returns The block type.
76
- */
77
- getTypeFromDefinition(
78
- blockDefinition: Blockly.utils.toolbox.BlockInfo
79
- ): string {
80
- if (blockDefinition["blockxml"]) {
81
- const xml =
82
- typeof blockDefinition["blockxml"] === "string"
83
- ? Blockly.utils.xml.textToDom(blockDefinition["blockxml"])
84
- : (blockDefinition["blockxml"] as Element);
85
- return xml.getAttribute("type");
86
- } else {
87
- return blockDefinition["type"];
88
- }
89
- }
90
-
91
- /**
92
- * Puts a previously created block into the recycle bin and moves it to the
93
- * top of the workspace. Used during large workspace swaps to limit the number
94
- * of new DOM elements we need to create.
95
- *
96
- * @param block The block to recycle.
97
- */
98
- recycleBlock(block: Blockly.BlockSvg) {
99
- const xy = block.getRelativeToSurfaceXY();
100
- block.moveBy(-xy.x, -xy.y);
101
- this.recycledBlocks.set(block.type, block);
102
- }
103
-
104
- /**
105
- * Returns a block from the cache of recycled blocks with the given type, or
106
- * undefined if one cannot be found.
107
- *
108
- * @param blockType The type of the block to try to recycle.
109
- * @returns The recycled block, or undefined if one could not be recycled.
110
- */
111
- getRecycledBlock(blockType: string): Blockly.BlockSvg | undefined {
112
- const block = this.recycledBlocks.get(blockType);
113
- this.recycledBlocks.delete(blockType);
114
- return block;
115
- }
116
-
117
- /**
118
- * Returns whether the given block can be recycled or not.
119
- *
120
- * @param block The block to check for recyclability.
121
- * @returns True if the block can be recycled. False otherwise.
122
- */
123
- blockIsRecyclable(block: Blockly.Block): boolean {
124
- if (!this.recyclingEnabled) {
125
- return false;
126
- }
127
-
128
- // If the block needs to parse mutations, never recycle.
129
- if (block.mutationToDom && block.domToMutation) {
130
- return false;
131
- }
132
-
133
- if (!block.isEnabled()) {
134
- return false;
135
- }
136
-
137
- for (const input of block.inputList) {
138
- for (const field of input.fieldRow) {
139
- // No variables.
140
- if (field.referencesVariables()) {
141
- return false;
142
- }
143
- if (field instanceof Blockly.FieldDropdown) {
144
- if (field.isOptionListDynamic()) {
145
- return false;
146
- }
147
- }
148
- }
149
- // Check children.
150
- if (input.connection) {
151
- const targetBlock = input.connection.targetBlock();
152
- if (targetBlock && !this.blockIsRecyclable(targetBlock)) {
153
- return false;
154
- }
155
- }
156
- }
157
- return true;
158
- }
159
-
160
- /**
161
- * Disposes of the provided block.
162
- *
163
- * @param element The block to dispose of.
164
- */
165
- disposeElement(element: Blockly.BlockSvg) {
166
- if (this.blockIsRecyclable(element)) {
167
- this.removeListeners(element.id);
168
- this.recycleBlock(element);
169
- } else {
170
- super.disposeElement(element);
171
- }
172
- }
173
-
174
- /**
175
- * Clears the cache of recycled blocks.
176
- */
177
- emptyRecycledBlocks() {
178
- this.recycledBlocks.forEach((block) => block.dispose(false, false));
179
- this.recycledBlocks.clear();
36
+ return flyoutItem;
180
37
  }
181
38
  }
182
39
 
@@ -7,6 +7,7 @@
7
7
  import * as Blockly from "blockly/core";
8
8
  import { ContinuousToolbox } from "@blockly/continuous-toolbox";
9
9
  import { ScratchContinuousCategory } from "./scratch_continuous_category";
10
+ import { STATUS_INDICATOR_LABEL_TYPE } from "./status_indicator_label_flyout_inflater";
10
11
 
11
12
  /**
12
13
  * A toolbox that displays items from all categories in one scrolling list.
@@ -23,36 +24,25 @@ export class ScratchContinuousToolbox extends ContinuousToolbox {
23
24
  }
24
25
 
25
26
  /**
26
- * Gets the contents that should be shown in the flyout.
27
+ * Converts the given toolbox item to a corresponding array of items that
28
+ * should appear in the flyout.
27
29
  *
28
- * @returns Flyout contents.
30
+ * @param toolboxItem The toolbox item to convert.
31
+ * @returns An array of flyout item definitions.
29
32
  */
30
- getInitialFlyoutContents_(): Blockly.utils.toolbox.FlyoutItemInfoArray {
31
- // TODO(#211) Clean this up when the continuous toolbox plugin is updated.
32
- let contents: Blockly.utils.toolbox.FlyoutItemInfoArray = [];
33
- for (const toolboxItem of this.getToolboxItems()) {
34
- if (toolboxItem instanceof ScratchContinuousCategory) {
35
- if (toolboxItem.shouldShowStatusButton()) {
36
- contents.push({
37
- kind: "STATUS_INDICATOR_LABEL",
38
- id: toolboxItem.getId(),
39
- text: toolboxItem.getName(),
40
- });
41
- } else {
42
- // Create a label node to go at the top of the category
43
- contents.push({ kind: "LABEL", text: toolboxItem.getName() });
44
- }
45
- let itemContents = toolboxItem.getContents();
46
-
47
- // Handle custom categories (e.g. variables and functions)
48
- if (typeof itemContents === "string") {
49
- itemContents = {
50
- custom: itemContents,
51
- kind: "CATEGORY",
52
- };
53
- }
54
- contents = contents.concat(itemContents);
55
- }
33
+ protected convertToolboxItemToFlyoutItems(
34
+ toolboxItem: Blockly.IToolboxItem
35
+ ): Blockly.utils.toolbox.FlyoutItemInfoArray {
36
+ const contents = super.convertToolboxItemToFlyoutItems(toolboxItem);
37
+ if (
38
+ toolboxItem instanceof ScratchContinuousCategory &&
39
+ toolboxItem.shouldShowStatusButton()
40
+ ) {
41
+ contents.splice(0, 1, {
42
+ kind: STATUS_INDICATOR_LABEL_TYPE,
43
+ id: toolboxItem.getId(),
44
+ text: toolboxItem.getName(),
45
+ });
56
46
  }
57
47
  return contents;
58
48
  }
@@ -62,12 +52,12 @@ export class ScratchContinuousToolbox extends ContinuousToolbox {
62
52
  */
63
53
  forceRerender() {
64
54
  const selectedCategoryName = this.selectedItem_?.getName();
65
- super.refreshSelection();
55
+ this.getFlyout().show(this.getInitialFlyoutContents());
56
+ this.selectCategoryByName(selectedCategoryName);
66
57
  let callback;
67
58
  while ((callback = this.postRenderCallbacks.shift())) {
68
59
  callback();
69
60
  }
70
- this.selectCategoryByName(selectedCategoryName);
71
61
  }
72
62
 
73
63
  /**
@@ -7,6 +7,8 @@
7
7
  import * as Blockly from "blockly/core";
8
8
  import { StatusIndicatorLabel } from "./status_indicator_label";
9
9
 
10
+ export const STATUS_INDICATOR_LABEL_TYPE = "status_indicator_label";
11
+
10
12
  /**
11
13
  * Flyout inflater responsible for creating status indicator labels.
12
14
  */
@@ -21,14 +23,14 @@ class StatusIndicatorLabelFlyoutInflater extends Blockly.LabelFlyoutInflater {
21
23
  load(
22
24
  state: Blockly.utils.toolbox.LabelInfo,
23
25
  flyoutWorkspace: Blockly.WorkspaceSvg
24
- ): StatusIndicatorLabel {
26
+ ): Blockly.FlyoutItem {
25
27
  const label = new StatusIndicatorLabel(
26
28
  flyoutWorkspace,
27
29
  flyoutWorkspace.targetWorkspace,
28
30
  state
29
31
  );
30
32
  label.show();
31
- return label;
33
+ return new Blockly.FlyoutItem(label, STATUS_INDICATOR_LABEL_TYPE, true);
32
34
  }
33
35
  }
34
36
 
@@ -38,7 +40,7 @@ class StatusIndicatorLabelFlyoutInflater extends Blockly.LabelFlyoutInflater {
38
40
  export function registerStatusIndicatorLabelFlyoutInflater() {
39
41
  Blockly.registry.register(
40
42
  Blockly.registry.Type.FLYOUT_INFLATER,
41
- "status_indicator_label",
43
+ STATUS_INDICATOR_LABEL_TYPE,
42
44
  StatusIndicatorLabelFlyoutInflater
43
45
  );
44
46
  }
package/src/variables.ts CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  } from "./constants";
30
30
  import { ScratchVariableModel } from "./scratch_variable_model";
31
31
  import { ScratchContinuousToolbox } from "./scratch_continuous_toolbox";
32
- import { CheckableContinuousFlyout } from "./checkable_continuous_flyout.js";
32
+ import { CheckableContinuousFlyout } from "./checkable_continuous_flyout";
33
33
 
34
34
  /**
35
35
  * Constant prefix to differentiate cloud variable names from other types of