scratch-blocks 2.0.0-spork.2 → 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.
Files changed (85) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/main.js +1 -1
  3. package/dist/main.js.LICENSE.txt +0 -12
  4. package/package.json +4 -4
  5. package/src/{block_reporting.js → block_reporting.ts} +7 -5
  6. package/src/blocks/{colour.js → colour.ts} +6 -6
  7. package/src/blocks/{control.js → control.ts} +21 -54
  8. package/src/blocks/{data.js → data.ts} +134 -142
  9. package/src/blocks/{event.js → event.ts} +12 -33
  10. package/src/blocks/{looks.js → looks.ts} +24 -73
  11. package/src/blocks/{math.js → math.ts} +6 -11
  12. package/src/blocks/{matrix.js → matrix.ts} +2 -3
  13. package/src/blocks/{motion.js → motion.ts} +23 -70
  14. package/src/blocks/{note.js → note.ts} +2 -3
  15. package/src/blocks/{operators.js → operators.ts} +18 -55
  16. package/src/blocks/{procedures.js → procedures.ts} +418 -269
  17. package/src/blocks/{sensing.js → sensing.ts} +21 -61
  18. package/src/blocks/{sound.js → sound.ts} +9 -28
  19. package/src/blocks/{text.js → text.ts} +1 -2
  20. package/src/blocks/{vertical_extensions.js → vertical_extensions.ts} +63 -100
  21. package/src/checkable_continuous_flyout.ts +112 -0
  22. package/src/{checkbox_bubble.js → checkbox_bubble.ts} +40 -58
  23. package/src/{colours.js → colours.ts} +11 -4
  24. package/src/{constants.js → constants.ts} +13 -0
  25. package/src/{context_menu_items.js → context_menu_items.ts} +18 -12
  26. package/src/{css.js → css.ts} +13 -7
  27. package/src/{data_category.js → data_category.ts} +216 -150
  28. package/src/events/{events_block_comment_base.js → events_block_comment_base.ts} +23 -4
  29. package/src/events/{events_block_comment_change.js → events_block_comment_change.ts} +29 -5
  30. package/src/events/{events_block_comment_collapse.js → events_block_comment_collapse.ts} +24 -6
  31. package/src/events/{events_block_comment_create.js → events_block_comment_create.ts} +36 -10
  32. package/src/events/{events_block_comment_delete.js → events_block_comment_delete.ts} +6 -2
  33. package/src/events/{events_block_comment_move.js → events_block_comment_move.ts} +36 -6
  34. package/src/events/events_block_comment_resize.ts +88 -0
  35. package/src/events/events_block_drag_end.ts +49 -0
  36. package/src/events/events_block_drag_outside.ts +44 -0
  37. package/src/events/{events_scratch_variable_create.js → events_scratch_variable_create.ts} +28 -15
  38. package/src/fields/{field_colour_slider.js → field_colour_slider.ts} +117 -106
  39. package/src/fields/{field_matrix.js → field_matrix.ts} +189 -215
  40. package/src/fields/{field_note.js → field_note.ts} +227 -286
  41. package/src/fields/{field_textinput_removable.js → field_textinput_removable.ts} +17 -20
  42. package/src/fields/{field_variable_getter.js → field_variable_getter.ts} +28 -17
  43. package/src/fields/{field_vertical_separator.js → field_vertical_separator.ts} +14 -30
  44. package/src/fields/{field_angle.js → scratch_field_angle.ts} +124 -80
  45. package/src/fields/{field_dropdown.js → scratch_field_dropdown.ts} +9 -7
  46. package/src/fields/{field_number.js → scratch_field_number.ts} +60 -55
  47. package/src/fields/{field_variable.js → scratch_field_variable.ts} +46 -27
  48. package/src/{flyout_checkbox_icon.js → flyout_checkbox_icon.ts} +15 -19
  49. package/src/{glows.js → glows.ts} +29 -18
  50. package/src/index.ts +62 -63
  51. package/src/procedures.ts +462 -0
  52. package/src/recyclable_block_flyout_inflater.ts +51 -0
  53. package/src/renderer/{bowler_hat.js → bowler_hat.ts} +1 -1
  54. package/src/renderer/{constants.js → constants.ts} +26 -12
  55. package/src/renderer/{drawer.js → drawer.ts} +8 -3
  56. package/src/renderer/{path_object.js → path_object.ts} +2 -2
  57. package/src/renderer/{render_info.js → render_info.ts} +19 -7
  58. package/src/renderer/renderer.ts +76 -0
  59. package/src/{scratch_block_paster.js → scratch_block_paster.ts} +9 -7
  60. package/src/scratch_blocks_utils.ts +39 -0
  61. package/src/{scratch_comment_icon.js → scratch_comment_icon.ts} +43 -26
  62. package/src/scratch_connection_checker.ts +44 -0
  63. package/src/{scratch_continuous_category.js → scratch_continuous_category.ts} +20 -13
  64. package/src/scratch_continuous_toolbox.ts +70 -0
  65. package/src/{scratch_dragger.js → scratch_dragger.ts} +97 -28
  66. package/src/{scratch_variable_map.js → scratch_variable_map.ts} +4 -1
  67. package/src/scratch_variable_model.ts +30 -0
  68. package/src/{shadows.js → shadows.ts} +8 -4
  69. package/src/{status_indicator_label.js → status_indicator_label.ts} +24 -36
  70. package/src/{status_indicator_label_flyout_inflater.js → status_indicator_label_flyout_inflater.ts} +13 -9
  71. package/src/{variables.js → variables.ts} +153 -123
  72. package/tsconfig.json +5 -0
  73. package/src/categories.js +0 -15
  74. package/src/checkable_continuous_flyout.js +0 -138
  75. package/src/events/events_block_comment_resize.js +0 -52
  76. package/src/events/events_block_drag_end.js +0 -33
  77. package/src/events/events_block_drag_outside.js +0 -30
  78. package/src/procedures.js +0 -425
  79. package/src/recyclable_block_flyout_inflater.js +0 -194
  80. package/src/renderer/renderer.js +0 -74
  81. package/src/scratch_blocks_utils.js +0 -148
  82. package/src/scratch_connection_checker.js +0 -29
  83. package/src/scratch_continuous_toolbox.js +0 -78
  84. package/src/scratch_variable_model.js +0 -24
  85. /package/{continuous-toolbox.d.ts → types/continuous-toolbox.d.ts} +0 -0
@@ -5,7 +5,6 @@
5
5
  */
6
6
 
7
7
  import * as Blockly from "blockly/core";
8
- import { cssVarify } from "../colours.js";
9
8
 
10
9
  export class ConstantProvider extends Blockly.zelos.ConstantProvider {
11
10
  REPLACEMENT_GLOW_COLOUR = "#ffffff";
@@ -18,27 +17,42 @@ export class ConstantProvider extends Blockly.zelos.ConstantProvider {
18
17
  * styles contain any raw color values, corresponding CSS variables will be
19
18
  * created/overridden so that those colors can be dynamically referenced in
20
19
  * stylesheets.
21
- * @param {!Blockly.Theme} The new theme to apply.
20
+ *
21
+ * @param theme The new theme to apply.
22
22
  */
23
- setTheme(theme) {
24
- const root = document.querySelector(":root");
23
+ setTheme(theme: Blockly.Theme) {
24
+ const root = document.querySelector(":root") as HTMLElement;
25
25
  for (const [key, colour] of Object.entries(theme.blockStyles)) {
26
- if (typeof colour === "string") {
26
+ if (typeof colour !== "object") {
27
27
  const varKey = `--colour-${key}`;
28
28
  root.style.setProperty(varKey, colour);
29
29
  } else {
30
- theme.setBlockStyle(`${key}_selected`, {
31
- colourPrimary: colour.colourQuaternary ?? colour.colourTertiary,
32
- colourSecondary: colour.colourQuaternary ?? colour.colourTertiary,
33
- colourTertiary: colour.colourQuaternary ?? colour.colourTertiary,
34
- colourQuaternary: colour.colourQuaternary ?? colour.colourTertiary,
35
- });
30
+ const style = {
31
+ colourPrimary:
32
+ "colourQuaternary" in colour
33
+ ? `${colour.colourQuaternary}`
34
+ : colour.colourTertiary,
35
+ colourSecondary:
36
+ "colourQuaternary" in colour
37
+ ? `${colour.colourQuaternary}`
38
+ : colour.colourTertiary,
39
+ colourTertiary:
40
+ "colourQuaternary" in colour
41
+ ? `${colour.colourQuaternary}`
42
+ : colour.colourTertiary,
43
+ colourQuaternary:
44
+ "colourQuaternary" in colour
45
+ ? `${colour.colourQuaternary}`
46
+ : colour.colourTertiary,
47
+ hat: "",
48
+ };
49
+ theme.setBlockStyle(`${key}_selected`, style);
36
50
  }
37
51
  }
38
52
  super.setTheme(theme);
39
53
  }
40
54
 
41
- createDom(svg, tagName, selector) {
55
+ createDom(svg: SVGElement, tagName: string, selector: string) {
42
56
  super.createDom(svg, tagName, selector);
43
57
  this.selectedGlowFilterId = "";
44
58
  }
@@ -5,9 +5,11 @@
5
5
  */
6
6
 
7
7
  import * as Blockly from "blockly/core";
8
+ import type { RenderInfo } from "./render_info";
8
9
 
9
10
  export class Drawer extends Blockly.zelos.Drawer {
10
- drawStatementInput_(row) {
11
+ info_: RenderInfo;
12
+ drawStatementInput_(row: Blockly.blockRendering.Row) {
11
13
  if (this.info_.isBowlerHatBlock()) {
12
14
  // Bowler hat blocks have straight sides with no C-shape/indentation for
13
15
  // statement blocks.
@@ -18,14 +20,17 @@ export class Drawer extends Blockly.zelos.Drawer {
18
20
  }
19
21
  }
20
22
 
21
- drawRightSideRow_(row) {
23
+ drawRightSideRow_(row: Blockly.blockRendering.Row) {
22
24
  if (
23
25
  this.info_.isBowlerHatBlock() &&
24
26
  Blockly.blockRendering.Types.isSpacer(row)
25
27
  ) {
26
28
  // Multi-row bowler hat blocks are not supported, this may need
27
29
  // adjustment to do so.
28
- Blockly.blockRendering.Drawer.prototype.drawRightSideRow_.call(this, row);
30
+ this.outlinePath_ += Blockly.utils.svgPaths.lineOnAxis(
31
+ "V",
32
+ row.yPos + row.height
33
+ );
29
34
  } else {
30
35
  super.drawRightSideRow_(row);
31
36
  }
@@ -15,9 +15,9 @@ export class PathObject extends Blockly.zelos.PathObject {
15
15
  * Apply the stored colours to the block's path, taking into account whether
16
16
  * the paths belong to a shadow block.
17
17
  *
18
- * @param {!Blockly.BlockSvg} block The source block.
18
+ * @param block The source block.
19
19
  */
20
- applyColour(block) {
20
+ applyColour(block: Blockly.BlockSvg) {
21
21
  super.applyColour(block);
22
22
 
23
23
  // These blocks are special in that, while they are technically shadow
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import * as Blockly from "blockly/core";
8
- import { BowlerHat } from "./bowler_hat.js";
8
+ import { BowlerHat } from "./bowler_hat";
9
9
 
10
10
  export class RenderInfo extends Blockly.zelos.RenderInfo {
11
11
  populateTopRow_() {
@@ -51,11 +51,14 @@ export class RenderInfo extends Blockly.zelos.RenderInfo {
51
51
  Blockly.blockRendering.Types.isHat(e)
52
52
  );
53
53
  hat.width = this.width;
54
- this.topRow.measure(true);
54
+ this.topRow.measure();
55
55
  }
56
56
  }
57
57
 
58
- getInRowSpacing_(prev, next) {
58
+ getInRowSpacing_(
59
+ prev: Blockly.blockRendering.Measurable,
60
+ next: Blockly.blockRendering.Measurable
61
+ ): number {
59
62
  if (
60
63
  this.isBowlerHatBlock() &&
61
64
  ((prev && Blockly.blockRendering.Types.isHat(prev)) ||
@@ -68,7 +71,10 @@ export class RenderInfo extends Blockly.zelos.RenderInfo {
68
71
  return super.getInRowSpacing_(prev, next);
69
72
  }
70
73
 
71
- getSpacerRowHeight_(prev, next) {
74
+ getSpacerRowHeight_(
75
+ prev: Blockly.blockRendering.Row,
76
+ next: Blockly.blockRendering.Row
77
+ ): number {
72
78
  if (this.isBowlerHatBlock() && prev === this.topRow) {
73
79
  return 0;
74
80
  }
@@ -76,14 +82,20 @@ export class RenderInfo extends Blockly.zelos.RenderInfo {
76
82
  return super.getSpacerRowHeight_(prev, next);
77
83
  }
78
84
 
79
- getElemCenterline_(row, elem) {
85
+ getElemCenterline_(
86
+ row: Blockly.blockRendering.Row,
87
+ elem: Blockly.blockRendering.Measurable
88
+ ): number {
80
89
  if (this.isBowlerHatBlock() && Blockly.blockRendering.Types.isField(elem)) {
81
90
  return row.yPos + row.height / 2;
82
91
  } else if (
92
+ "isScratchExtension" in this.block_ &&
83
93
  this.block_.isScratchExtension &&
84
94
  Blockly.blockRendering.Types.isField(elem) &&
85
- elem.field instanceof Blockly.FieldImage &&
86
- elem.field === this.block_.inputList[0].fieldRow[0] &&
95
+ (elem as Blockly.blockRendering.Field).field instanceof
96
+ Blockly.FieldImage &&
97
+ (elem as Blockly.blockRendering.Field).field ===
98
+ this.block_.inputList[0].fieldRow[0] &&
87
99
  this.block_.previousConnection
88
100
  ) {
89
101
  // Vertically center the icon on extension blocks.
@@ -0,0 +1,76 @@
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 { Drawer } from "./drawer";
9
+ import { RenderInfo } from "./render_info";
10
+ import { ConstantProvider } from "./constants";
11
+ import { PathObject } from "./path_object";
12
+
13
+ /**
14
+ * Custom renderer for Scratch-style blocks.
15
+ */
16
+ export class ScratchRenderer extends Blockly.zelos.Renderer {
17
+ /**
18
+ * Create a new instance of the renderer's drawer.
19
+ *
20
+ * @param block The block to render.
21
+ * @param infoAn object containing all the information needed to render this
22
+ * block.
23
+ * @returns The drawer.
24
+ */
25
+ makeDrawer_(block: Blockly.BlockSvg, info: RenderInfo): Drawer {
26
+ return new Drawer(block, info);
27
+ }
28
+
29
+ /**
30
+ * Create a new instance of the renderer's render info object.
31
+ *
32
+ * @param block The block to measure.
33
+ * @returns The render info object.
34
+ */
35
+ makeRenderInfo_(block: Blockly.BlockSvg): RenderInfo {
36
+ return new RenderInfo(this, block);
37
+ }
38
+
39
+ /**
40
+ * Create a new instance of the renderer's constant provider.
41
+ *
42
+ * @returns The constant provider.
43
+ */
44
+ makeConstants_(): ConstantProvider {
45
+ return new ConstantProvider();
46
+ }
47
+
48
+ /**
49
+ * Create a new instance of a renderer path object.
50
+ *
51
+ * @param root The root SVG element.
52
+ * @param style The style object to use for colouring.
53
+ * @returns The renderer path object.
54
+ */
55
+ makePathObject(
56
+ root: SVGElement,
57
+ style: Blockly.Theme.BlockStyle
58
+ ): PathObject {
59
+ return new PathObject(root, style, this.getConstants());
60
+ }
61
+
62
+ /**
63
+ * Determine whether or not to highlight a connection.
64
+ *
65
+ * @param connection The connection to determine whether or not to highlight.
66
+ * @returns True if we should highlight the connection.
67
+ */
68
+ shouldHighlightConnection(connection: Blockly.RenderedConnection): boolean {
69
+ return (
70
+ connection.type === Blockly.ConnectionType.INPUT_VALUE &&
71
+ connection.getCheck()?.includes("Boolean")
72
+ );
73
+ }
74
+ }
75
+
76
+ Blockly.blockRendering.register("scratch", ScratchRenderer);
@@ -13,14 +13,16 @@ class ScratchBlockPaster extends Blockly.clipboard.BlockPaster {
13
13
  /**
14
14
  * Deserializes the given block data onto the workspace.
15
15
  *
16
- * @param {!Blockly.clipboard.BlockCopyData} copyData The serialized block
17
- * state to create a copy of on the workspace.
18
- * @param {!Blockly.WorkspaceSvg} workspace The workspace to paste the block
19
- * onto.
20
- * @param {?Blockly.utils.Coordinate} coordinate The location to paste the
21
- * block.
16
+ * @param copyData The serialized block state to create a copy of on the
17
+ * workspace.
18
+ * @param workspace The workspace to paste the block onto.
19
+ * @param coordinate The location to paste the block.
22
20
  */
23
- paste(copyData, workspace, coordinate) {
21
+ paste(
22
+ copyData: Blockly.clipboard.BlockCopyData,
23
+ workspace: Blockly.WorkspaceSvg,
24
+ coordinate: Blockly.utils.Coordinate
25
+ ) {
24
26
  const block = super.paste(copyData, workspace, coordinate);
25
27
  if (
26
28
  block?.type === "argument_reporter_boolean" ||
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @license
3
+ * Visual Blocks Editor
4
+ *
5
+ * Copyright 2018 Google Inc.
6
+ * https://developers.google.com/blockly/
7
+ *
8
+ * Licensed under the Apache License, Version 2.0 (the "License");
9
+ * you may not use this file except in compliance with the License.
10
+ * You may obtain a copy of the License at
11
+ *
12
+ * http://www.apache.org/licenses/LICENSE-2.0
13
+ *
14
+ * Unless required by applicable law or agreed to in writing, software
15
+ * distributed under the License is distributed on an "AS IS" BASIS,
16
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ * See the License for the specific language governing permissions and
18
+ * limitations under the License.
19
+ */
20
+
21
+ /**
22
+ * @fileoverview Utility methods for Scratch Blocks but not Blockly.
23
+ * @author fenichel@google.com (Rachel Fenichel)
24
+ */
25
+ import * as Blockly from "blockly/core";
26
+
27
+ /**
28
+ * Compare strings with natural number sorting.
29
+ *
30
+ * @param str1 First input.
31
+ * @param str2 Second input.
32
+ * @returns -1, 0, or 1 to signify greater than, equality, or less than.
33
+ */
34
+ export function compareStrings(str1: string, str2: string): number {
35
+ return str1.localeCompare(str2, [], {
36
+ sensitivity: "base",
37
+ numeric: true,
38
+ });
39
+ }
@@ -7,15 +7,32 @@
7
7
  import * as Blockly from "blockly/core";
8
8
  import { ScratchCommentBubble } from "./scratch_comment_bubble.js";
9
9
 
10
+ interface CommentState {
11
+ text: string;
12
+ height: number;
13
+ width: number;
14
+ x: number;
15
+ y: number;
16
+ collapsed: boolean;
17
+ }
18
+
10
19
  /**
11
20
  * Custom comment icon that draws no icon indicator, used for block comments.
12
- * @implements {IHasBubble}
13
- * @implements {ISerializable}
14
21
  */
15
- class ScratchCommentIcon extends Blockly.icons.Icon {
16
- constructor(sourceBlock) {
22
+ class ScratchCommentIcon
23
+ extends Blockly.icons.Icon
24
+ implements Blockly.ISerializable, Blockly.IHasBubble
25
+ {
26
+ private commentBubble: ScratchCommentBubble;
27
+ private onTextChangedListener: (oldText: string, newText: string) => void;
28
+ private onSizeChangedListener: (
29
+ oldSize: Blockly.utils.Size,
30
+ newSize: Blockly.utils.Size
31
+ ) => void;
32
+ private onCollapseListener: (collapsed: boolean) => void;
33
+
34
+ constructor(protected sourceBlock: Blockly.BlockSvg) {
17
35
  super(sourceBlock);
18
- this.sourceBlock = sourceBlock;
19
36
  this.commentBubble = new ScratchCommentBubble(this.sourceBlock);
20
37
  Blockly.Events.fire(
21
38
  new (Blockly.Events.get("block_comment_create"))(this.commentBubble)
@@ -28,28 +45,28 @@ class ScratchCommentIcon extends Blockly.icons.Icon {
28
45
  this.commentBubble.addOnCollapseListener(this.onCollapseListener);
29
46
  }
30
47
 
31
- getType() {
48
+ getType(): Blockly.icons.IconType<ScratchCommentIcon> {
32
49
  return Blockly.icons.IconType.COMMENT;
33
50
  }
34
51
 
35
- initView(pointerDownListener) {
52
+ initView(pointerDownListener: (e: PointerEvent) => void) {
36
53
  // Scratch comments have no indicator icon on the block.
37
54
  return;
38
55
  }
39
56
 
40
- getSize() {
57
+ getSize(): Blockly.utils.Size {
41
58
  // Awful hack to cancel out the default padding added to icons.
42
59
  return new Blockly.utils.Size(-8, 0);
43
60
  }
44
61
 
45
- getAnchorPoint() {
62
+ getAnchorPoint(): Blockly.utils.Coordinate {
46
63
  const blockRect = this.sourceBlock.getBoundingRectangleWithoutChildren();
47
64
  const y = blockRect.top + this.offsetInBlock.y;
48
65
  const x = this.sourceBlock.workspace.RTL ? blockRect.left : blockRect.right;
49
66
  return new Blockly.utils.Coordinate(x, y);
50
67
  }
51
68
 
52
- onLocationChange(blockOrigin) {
69
+ onLocationChange(blockOrigin: Blockly.utils.Coordinate) {
53
70
  if (!this.sourceBlock || !this.commentBubble) return;
54
71
 
55
72
  if (this.sourceBlock.isInsertionMarker()) {
@@ -70,15 +87,15 @@ class ScratchCommentIcon extends Blockly.icons.Icon {
70
87
  );
71
88
  }
72
89
 
73
- setText(text) {
90
+ setText(text: string) {
74
91
  this.commentBubble?.setText(text);
75
92
  }
76
93
 
77
- getText() {
94
+ getText(): string {
78
95
  return this.commentBubble?.getText() ?? "";
79
96
  }
80
97
 
81
- onTextChanged(oldText, newText) {
98
+ onTextChanged(oldText: string, newText: string) {
82
99
  Blockly.Events.fire(
83
100
  new (Blockly.Events.get(Blockly.Events.BLOCK_CHANGE))(
84
101
  this.sourceBlock,
@@ -97,7 +114,7 @@ class ScratchCommentIcon extends Blockly.icons.Icon {
97
114
  );
98
115
  }
99
116
 
100
- onCollapsed(collapsed) {
117
+ onCollapsed(collapsed: boolean) {
101
118
  Blockly.Events.fire(
102
119
  new (Blockly.Events.get("block_comment_collapse"))(
103
120
  this.commentBubble,
@@ -106,7 +123,7 @@ class ScratchCommentIcon extends Blockly.icons.Icon {
106
123
  );
107
124
  }
108
125
 
109
- onSizeChanged(oldSize, newSize) {
126
+ onSizeChanged(oldSize: Blockly.utils.Size, newSize: Blockly.utils.Size) {
110
127
  Blockly.Events.fire(
111
128
  new (Blockly.Events.get("block_comment_resize"))(
112
129
  this.commentBubble,
@@ -116,15 +133,15 @@ class ScratchCommentIcon extends Blockly.icons.Icon {
116
133
  );
117
134
  }
118
135
 
119
- setBubbleSize(size) {
136
+ setBubbleSize(size: Blockly.utils.Size) {
120
137
  this.commentBubble?.setSize(size);
121
138
  }
122
139
 
123
- getBubbleSize() {
140
+ getBubbleSize(): Blockly.utils.Size {
124
141
  return this.commentBubble?.getSize() ?? new Blockly.utils.Size(0, 0);
125
142
  }
126
143
 
127
- setBubbleLocation(newLocation) {
144
+ setBubbleLocation(newLocation: Blockly.utils.Coordinate) {
128
145
  const oldLocation = this.getBubbleLocation();
129
146
  this.commentBubble?.moveTo(newLocation);
130
147
  Blockly.Events.fire(
@@ -136,11 +153,11 @@ class ScratchCommentIcon extends Blockly.icons.Icon {
136
153
  );
137
154
  }
138
155
 
139
- getBubbleLocation() {
156
+ getBubbleLocation(): Blockly.utils.Coordinate {
140
157
  return this.commentBubble?.getRelativeToSurfaceXY();
141
158
  }
142
159
 
143
- saveState() {
160
+ saveState(): CommentState | null {
144
161
  if (!this.commentBubble) return null;
145
162
 
146
163
  const size = this.getBubbleSize();
@@ -159,7 +176,8 @@ class ScratchCommentIcon extends Blockly.icons.Icon {
159
176
  };
160
177
  }
161
178
 
162
- loadState(state) {
179
+ loadState(state: CommentState) {
180
+ Blockly.Events.setGroup(true);
163
181
  this.setText(state["text"]);
164
182
  this.setBubbleSize(new Blockly.utils.Size(state["width"], state["height"]));
165
183
  const delta = new Blockly.utils.Coordinate(state["x"], state["y"]);
@@ -169,20 +187,19 @@ class ScratchCommentIcon extends Blockly.icons.Icon {
169
187
  );
170
188
  this.commentBubble.moveTo(newBubbleLocation);
171
189
  this.commentBubble.setCollapsed(state["collapsed"]);
190
+ Blockly.Events.setGroup(false);
172
191
  }
173
192
 
174
- bubbleIsVisible() {
193
+ bubbleIsVisible(): boolean {
175
194
  return true;
176
195
  }
177
196
 
178
- async setBubbleVisible(visible) {
197
+ async setBubbleVisible(visible: boolean) {
179
198
  this.commentBubble.setCollapsed(!visible);
180
199
  }
181
200
 
182
201
  dispose() {
183
- this.commentBubble?.dispose();
184
- this.commentBubble = null;
185
- this.sourceBlock = null;
202
+ this.commentBubble.dispose();
186
203
  super.dispose();
187
204
  }
188
205
  }
@@ -0,0 +1,44 @@
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
+
9
+ /**
10
+ * Custom connection checker to restrict which blocks can be connected.
11
+ */
12
+ class ScratchConnectionChecker extends Blockly.ConnectionChecker {
13
+ /**
14
+ * Returns whether or not the two connections should be allowed to connect.
15
+ *
16
+ * @param a One of the connections to check.
17
+ * @param b The other connection to check.
18
+ * @param distance The maximum allowable distance between connections.
19
+ * @returns True if the connections should be allowed to connect.
20
+ */
21
+ doDragChecks(
22
+ a: Blockly.RenderedConnection,
23
+ b: Blockly.RenderedConnection,
24
+ distance: number
25
+ ): boolean {
26
+ // This check prevents dragging a block into the slot occupied by the
27
+ // procedure caller example block in a procedure definition block.
28
+ if (
29
+ b.getSourceBlock().type === "procedures_definition" &&
30
+ b.getParentInput()?.name === "custom_block"
31
+ ) {
32
+ return false;
33
+ }
34
+
35
+ return super.doDragChecks(a, b, distance);
36
+ }
37
+ }
38
+
39
+ Blockly.registry.register(
40
+ Blockly.registry.Type.CONNECTION_CHECKER,
41
+ Blockly.registry.DEFAULT,
42
+ ScratchConnectionChecker,
43
+ true
44
+ );
@@ -7,34 +7,41 @@
7
7
  import * as Blockly from "blockly/core";
8
8
  import { ContinuousCategory } from "@blockly/continuous-toolbox";
9
9
 
10
+ type StatusIndicatorCategoryInfo = Blockly.utils.toolbox.CategoryInfo & {
11
+ showStatusButton?: string;
12
+ };
13
+
14
+ /**
15
+ * Selectable category shown in the Scratch toolbox.
16
+ */
10
17
  export class ScratchContinuousCategory extends ContinuousCategory {
11
18
  /**
12
19
  * Whether this toolbox category has a status indicator button on its label
13
20
  * in the flyout, typically for extensions that interface with hardware
14
21
  * devices.
15
- * @type {boolean}
16
22
  */
17
- showStatusButton = false;
23
+ private showStatusButton = false;
18
24
 
19
25
  /** Creates a new ScratchContinuousCategory.
20
26
  *
21
- * @param {!Blockly.toolbox.CategoryInfo} toolboxItemDef A toolbox item
22
- * definition.
23
- * @param {!Blockly.Toolbox} parentToolbox The toolbox this category is being
24
- * added to.
25
- * @param {?Blockly.ICollapsibleToolboxItem} opt_parent The parent toolbox
26
- * category, if any.
27
+ * @param toolboxItemDef A toolbox item definition.
28
+ * @param parentToolbox The toolbox this category is being added to.
29
+ * @param opt_parent The parent toolbox category, if any.
27
30
  */
28
- constructor(toolboxItemDef, parentToolbox, opt_parent) {
31
+ constructor(
32
+ toolboxItemDef: StatusIndicatorCategoryInfo,
33
+ parentToolbox: Blockly.Toolbox,
34
+ opt_parent?: Blockly.ICollapsibleToolboxItem
35
+ ) {
29
36
  super(toolboxItemDef, parentToolbox, opt_parent);
30
37
  this.showStatusButton = toolboxItemDef["showStatusButton"] === "true";
31
38
  }
32
39
 
33
40
  /**
34
41
  * Creates a DOM element for this category's icon.
35
- * @returns {!HTMLElement} A DOM element for this category's icon.
42
+ * @returns A DOM element for this category's icon.
36
43
  */
37
- createIconDom_() {
44
+ createIconDom_(): HTMLElement {
38
45
  if (this.toolboxItemDef_.iconURI) {
39
46
  const icon = document.createElement("img");
40
47
  icon.src = this.toolboxItemDef_.iconURI;
@@ -49,9 +56,9 @@ export class ScratchContinuousCategory extends ContinuousCategory {
49
56
 
50
57
  /**
51
58
  * Sets whether or not this category is selected.
52
- * @param {boolean} isSelected True if this category is selected.
59
+ * @param isSelected True if this category is selected.
53
60
  */
54
- setSelected(isSelected) {
61
+ setSelected(isSelected: boolean) {
55
62
  super.setSelected(isSelected);
56
63
  // Prevent hardcoding the background color to grey.
57
64
  this.rowDiv_.style.backgroundColor = "";
@@ -0,0 +1,70 @@
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 { ContinuousToolbox } from "@blockly/continuous-toolbox";
9
+ import { ScratchContinuousCategory } from "./scratch_continuous_category";
10
+ import { STATUS_INDICATOR_LABEL_TYPE } from "./status_indicator_label_flyout_inflater";
11
+
12
+ /**
13
+ * A toolbox that displays items from all categories in one scrolling list.
14
+ */
15
+ export class ScratchContinuousToolbox extends ContinuousToolbox {
16
+ /**
17
+ * List of functions to run after the next time the toolbox renders.
18
+ */
19
+ private postRenderCallbacks: (() => void)[] = [];
20
+
21
+ refreshSelection() {
22
+ // Intentionally a no-op, Scratch manually manages refreshing the toolbox
23
+ // via forceRerender().
24
+ }
25
+
26
+ /**
27
+ * Converts the given toolbox item to a corresponding array of items that
28
+ * should appear in the flyout.
29
+ *
30
+ * @param toolboxItem The toolbox item to convert.
31
+ * @returns An array of flyout item definitions.
32
+ */
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
+ });
46
+ }
47
+ return contents;
48
+ }
49
+
50
+ /**
51
+ * Forcibly rerenders the toolbox, preserving selection when possible.
52
+ */
53
+ forceRerender() {
54
+ const selectedCategoryName = this.selectedItem_?.getName();
55
+ this.getFlyout().show(this.getInitialFlyoutContents());
56
+ this.selectCategoryByName(selectedCategoryName);
57
+ let callback;
58
+ while ((callback = this.postRenderCallbacks.shift())) {
59
+ callback();
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Runs the specified callback after the next rerender.
65
+ * @param callback A callback to run whenever the toolbox next rerenders.
66
+ */
67
+ runAfterRerender(callback: () => void) {
68
+ this.postRenderCallbacks.push(callback);
69
+ }
70
+ }