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

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.
@@ -11,17 +11,28 @@ import * as Blockly from "blockly/core";
11
11
  * @implements {IBubble}
12
12
  * @implements {ISelectable}
13
13
  */
14
- export class ScratchCommentBubble extends Blockly.comments.CommentView {
15
- constructor(sourceBlock) {
16
- super(sourceBlock.workspace);
14
+ export class ScratchCommentBubble
15
+ extends Blockly.comments.CommentView
16
+ implements Blockly.IBubble, Blockly.ISelectable
17
+ {
18
+ id: string;
19
+ private sourceBlock: Blockly.BlockSvg;
20
+ private anchor?: Blockly.utils.Coordinate;
21
+ private anchorChain?: SVGLineElement;
22
+ private dragStartLocation?: Blockly.utils.Coordinate;
23
+
24
+ constructor(sourceBlock: Blockly.BlockSvg) {
25
+ const commentId = `${sourceBlock.id}_comment`;
26
+ super(sourceBlock.workspace, commentId);
17
27
  this.sourceBlock = sourceBlock;
18
28
  this.disposing = false;
19
- this.id = Blockly.utils.idGenerator.genUid();
29
+ this.id = commentId;
20
30
  this.setPlaceholderText(Blockly.Msg.WORKSPACE_COMMENT_DEFAULT_TEXT);
21
31
  this.getSvgRoot().setAttribute(
22
32
  "style",
23
33
  `--colour-commentBorder: ${sourceBlock.getColourTertiary()};`
24
34
  );
35
+ this.getSvgRoot().setAttribute("id", this.id);
25
36
 
26
37
  Blockly.browserEvents.conditionalBind(
27
38
  this.getSvgRoot(),
@@ -34,15 +45,15 @@ export class ScratchCommentBubble extends Blockly.comments.CommentView {
34
45
  this.getSvgRoot(),
35
46
  "wheel",
36
47
  this,
37
- (e) => {
48
+ (e: WheelEvent) => {
38
49
  e.stopPropagation();
39
50
  }
40
51
  );
41
52
  }
42
53
 
43
- setDeleteStyle(enable) {}
54
+ setDeleteStyle(enable: boolean) {}
44
55
  showContextMenu() {}
45
- setDragging(start) {}
56
+ setDragging(start: boolean) {}
46
57
  select() {}
47
58
  unselect() {}
48
59
 
@@ -50,11 +61,13 @@ export class ScratchCommentBubble extends Blockly.comments.CommentView {
50
61
  return true;
51
62
  }
52
63
 
53
- moveDuringDrag(newLocation) {
64
+ moveDuringDrag(newLocation: Blockly.utils.Coordinate) {
54
65
  this.moveTo(newLocation);
55
66
  }
56
67
 
57
- moveTo(xOrCoordinate, y) {
68
+ moveTo(xOrCoordinate: number, y: number): void;
69
+ moveTo(xOrCoordinate: Blockly.utils.Coordinate): void;
70
+ moveTo(xOrCoordinate: Blockly.utils.Coordinate | number, y?: number) {
58
71
  const destination =
59
72
  xOrCoordinate instanceof Blockly.utils.Coordinate
60
73
  ? xOrCoordinate
@@ -63,22 +76,22 @@ export class ScratchCommentBubble extends Blockly.comments.CommentView {
63
76
  this.redrawAnchorChain();
64
77
  }
65
78
 
66
- startGesture(e) {
79
+ startGesture(e: PointerEvent) {
67
80
  const gesture = this.workspace.getGesture(e);
68
81
  if (gesture) {
69
- gesture.handleCommentStart(e, this);
82
+ gesture.handleBubbleStart(e, this);
70
83
  Blockly.common.setSelected(this);
71
84
  }
72
85
  }
73
86
 
74
- startDrag(event) {
87
+ startDrag(event: PointerEvent) {
75
88
  this.dragStartLocation = this.getRelativeToSurfaceXY();
76
89
  this.workspace.setResizesEnabled(false);
77
90
  this.workspace.getLayerManager()?.moveToDragLayer(this);
78
91
  Blockly.utils.dom.addClass(this.getSvgRoot(), "blocklyDragging");
79
92
  }
80
93
 
81
- drag(newLocation, event) {
94
+ drag(newLocation: Blockly.utils.Coordinate, event: Event) {
82
95
  this.moveTo(newLocation);
83
96
  }
84
97
 
@@ -101,7 +114,7 @@ export class ScratchCommentBubble extends Blockly.comments.CommentView {
101
114
  this.moveTo(this.dragStartLocation);
102
115
  }
103
116
 
104
- setAnchorLocation(newAnchor) {
117
+ setAnchorLocation(newAnchor: Blockly.utils.Coordinate) {
105
118
  const oldAnchor = this.anchor;
106
119
  const alreadyAnchored = !!this.anchor;
107
120
  this.anchor = newAnchor;
@@ -116,15 +129,19 @@ export class ScratchCommentBubble extends Blockly.comments.CommentView {
116
129
  }
117
130
 
118
131
  dropAnchor() {
119
- this.moveTo(this.anchor.x + 40, this.anchor.y - 16);
132
+ const verticalOffset = 16;
133
+ this.moveTo(
134
+ this.anchor.x + 40 * (this.workspace.RTL ? -1 : 1),
135
+ this.anchor.y - verticalOffset
136
+ );
120
137
  const location = this.getRelativeToSurfaceXY();
121
138
  this.anchorChain = Blockly.utils.dom.createSvgElement(
122
139
  Blockly.utils.Svg.LINE,
123
140
  {
124
141
  x1: this.anchor.x - location.x,
125
142
  y1: this.anchor.y - location.y,
126
- x2: this.getSize().width / 2,
127
- y2: 16,
143
+ x2: (this.getSize().width / 2) * (this.workspace.RTL ? -1 : 1),
144
+ y2: verticalOffset,
128
145
  style: `stroke: ${this.sourceBlock.getColourTertiary()}; stroke-width: 1`,
129
146
  },
130
147
  this.getSvgRoot()
@@ -139,8 +156,8 @@ export class ScratchCommentBubble extends Blockly.comments.CommentView {
139
156
  if (!this.anchorChain) return;
140
157
 
141
158
  const location = this.getRelativeToSurfaceXY();
142
- this.anchorChain.setAttribute("x1", this.anchor.x - location.x);
143
- this.anchorChain.setAttribute("y1", this.anchor.y - location.y);
159
+ this.anchorChain.setAttribute("x1", `${this.anchor.x - location.x}`);
160
+ this.anchorChain.setAttribute("y1", `${this.anchor.y - location.y}`);
144
161
  }
145
162
 
146
163
  getId() {
@@ -166,4 +183,20 @@ export class ScratchCommentBubble extends Blockly.comments.CommentView {
166
183
  }
167
184
  super.dispose();
168
185
  }
186
+
187
+ getFocusableElement() {
188
+ return this.getSvgRoot();
189
+ }
190
+
191
+ getFocusableTree() {
192
+ return this.workspace;
193
+ }
194
+
195
+ onNodeFocus() {}
196
+
197
+ onNodeBlur() {}
198
+
199
+ canBeFocused() {
200
+ return true;
201
+ }
169
202
  }
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import * as Blockly from "blockly/core";
8
- import { ScratchCommentBubble } from "./scratch_comment_bubble.js";
8
+ import { ScratchCommentBubble } from "./scratch_comment_bubble";
9
9
 
10
10
  interface CommentState {
11
11
  text: string;
@@ -19,7 +19,7 @@ interface CommentState {
19
19
  /**
20
20
  * Custom comment icon that draws no icon indicator, used for block comments.
21
21
  */
22
- class ScratchCommentIcon
22
+ export class ScratchCommentIcon
23
23
  extends Blockly.icons.Icon
24
24
  implements Blockly.ISerializable, Blockly.IHasBubble
25
25
  {
@@ -34,9 +34,7 @@ class ScratchCommentIcon
34
34
  constructor(protected sourceBlock: Blockly.BlockSvg) {
35
35
  super(sourceBlock);
36
36
  this.commentBubble = new ScratchCommentBubble(this.sourceBlock);
37
- Blockly.Events.fire(
38
- new (Blockly.Events.get("block_comment_create"))(this.commentBubble)
39
- );
37
+ this.fireCreateEvent();
40
38
  this.onTextChangedListener = this.onTextChanged.bind(this);
41
39
  this.onSizeChangedListener = this.onSizeChanged.bind(this);
42
40
  this.onCollapseListener = this.onCollapsed.bind(this);
@@ -198,10 +196,27 @@ class ScratchCommentIcon
198
196
  this.commentBubble.setCollapsed(!visible);
199
197
  }
200
198
 
199
+ getBubble() {
200
+ return this.commentBubble;
201
+ }
202
+
201
203
  dispose() {
202
204
  this.commentBubble.dispose();
203
205
  super.dispose();
204
206
  }
207
+
208
+ canBeFocused() {
209
+ return false;
210
+ }
211
+
212
+ /**
213
+ * Fires a block comment create event corresponding to this icon's comment.
214
+ */
215
+ fireCreateEvent() {
216
+ Blockly.Events.fire(
217
+ new (Blockly.Events.get("block_comment_create"))(this.commentBubble)
218
+ );
219
+ }
205
220
  }
206
221
 
207
222
  Blockly.registry.register(
@@ -67,4 +67,20 @@ export class ScratchContinuousToolbox extends ContinuousToolbox {
67
67
  runAfterRerender(callback: () => void) {
68
68
  this.postRenderCallbacks.push(callback);
69
69
  }
70
+
71
+ /**
72
+ * Returns whether or not the given item should be deselected.
73
+ * Prevents items from being deselected without a replacement.
74
+ *
75
+ * @param oldItem The item that was previously selected.
76
+ * @param newItem The item that is proposed to be selected instead.
77
+ * @returns True if the old item should be allowed to be deselected.
78
+ */
79
+ shouldDeselectItem_(
80
+ oldItem: Blockly.ISelectableToolboxItem | null,
81
+ newItem: Blockly.ISelectableToolboxItem | null
82
+ ) {
83
+ if (!newItem) return false;
84
+ return super.shouldDeselectItem_(oldItem, newItem);
85
+ }
70
86
  }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import * as Blockly from "blockly/core";
8
+
9
+ /**
10
+ * Displays an indicator of where a block being dragged will be connected.
11
+ */
12
+ class ScratchInsertionMarkerPreviewer extends Blockly.InsertionMarkerPreviewer {
13
+ /**
14
+ * Transforms the given block into a JSON representation used to construct an
15
+ * insertion marker.
16
+ *
17
+ * @param block The block to serialize and use as an insertion marker.
18
+ * @returns A JSON-formatted string corresponding to a serialized
19
+ * representation of the given block suitable for use as an insertion
20
+ * marker.
21
+ */
22
+ protected override serializeBlockToInsertionMarker(block: Blockly.BlockSvg) {
23
+ const blockJson = Blockly.serialization.blocks.save(block, {
24
+ addCoordinates: false,
25
+ addInputBlocks: true,
26
+ addNextBlocks: false,
27
+ doFullSerialization: false,
28
+ });
29
+
30
+ if (!blockJson) {
31
+ throw new Error(
32
+ `Failed to serialize source block. ${block.toDevString()}`
33
+ );
34
+ }
35
+
36
+ return blockJson;
37
+ }
38
+ }
39
+
40
+ Blockly.registry.register(
41
+ Blockly.registry.Type.CONNECTION_PREVIEWER,
42
+ Blockly.registry.DEFAULT,
43
+ ScratchInsertionMarkerPreviewer,
44
+ true
45
+ );
@@ -22,15 +22,15 @@ class StatusIndicatorLabelFlyoutInflater extends Blockly.LabelFlyoutInflater {
22
22
  */
23
23
  load(
24
24
  state: Blockly.utils.toolbox.LabelInfo,
25
- flyoutWorkspace: Blockly.WorkspaceSvg
25
+ flyout: Blockly.IFlyout
26
26
  ): Blockly.FlyoutItem {
27
27
  const label = new StatusIndicatorLabel(
28
- flyoutWorkspace,
29
- flyoutWorkspace.targetWorkspace,
28
+ flyout.getWorkspace(),
29
+ flyout.targetWorkspace,
30
30
  state
31
31
  );
32
32
  label.show();
33
- return new Blockly.FlyoutItem(label, STATUS_INDICATOR_LABEL_TYPE, true);
33
+ return new Blockly.FlyoutItem(label, STATUS_INDICATOR_LABEL_TYPE);
34
34
  }
35
35
  }
36
36
 
package/src/xml.ts ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import * as Blockly from "blockly/core";
8
+ import { ScratchVariableModel } from "./scratch_variable_model";
9
+
10
+ /**
11
+ * Clears the workspace and loads the given serialized state.
12
+ *
13
+ * @param xml XML representation of a Blockly workspace.
14
+ * @param workspace The workspace to load the serialized data onto.
15
+ */
16
+ export function clearWorkspaceAndLoadFromXml(
17
+ xml: Element,
18
+ workspace: Blockly.WorkspaceSvg
19
+ ): string[] {
20
+ workspace.setResizesEnabled(false);
21
+ Blockly.Events.setGroup(true);
22
+ workspace.clear();
23
+
24
+ // Manually load variables to include the cloud and local properties that core
25
+ // Blockly is unaware of.
26
+ for (const variable of xml.querySelectorAll("variables variable")) {
27
+ const id = variable.getAttribute("id");
28
+ if (!id) continue;
29
+ const type = variable.getAttribute("type");
30
+ const name = variable.textContent;
31
+ const isLocal = variable.getAttribute("islocal") === "true";
32
+ const isCloud = variable.getAttribute("iscloud") === "true";
33
+
34
+ const variableModel = new ScratchVariableModel(
35
+ workspace,
36
+ name,
37
+ type,
38
+ id,
39
+ isLocal,
40
+ isCloud
41
+ );
42
+ Blockly.Events.fire(
43
+ new (Blockly.Events.get(Blockly.Events.VAR_CREATE))(variableModel)
44
+ );
45
+ workspace.getVariableMap().addVariable(variableModel);
46
+ }
47
+
48
+ // Remove the `variables` element from the XML to prevent Blockly from
49
+ // throwing or stomping on the variables we created.
50
+ xml.querySelector("variables").remove();
51
+
52
+ // Defer to core for the rest of the deserialization.
53
+ const blockIds = Blockly.Xml.domToWorkspace(xml, workspace);
54
+
55
+ workspace.setResizesEnabled(true);
56
+ return blockIds;
57
+ }