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.
- package/.nvmrc +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.LICENSE.txt +6 -0
- package/msg/js/en.js +1 -0
- package/msg/json/en.json +2 -1
- package/msg/messages.js +1 -0
- package/msg/scratch_msgs.js +117 -37
- package/package.json +10 -10
- package/src/blocks/procedures.ts +1 -1
- package/src/blocks/sensing.ts +12 -0
- package/src/checkable_continuous_flyout.ts +4 -0
- package/src/checkbox_bubble.ts +21 -0
- package/src/colours.ts +1 -0
- package/src/context_menu_items.ts +24 -0
- package/src/css.ts +17 -14
- package/src/fields/field_matrix.ts +38 -27
- package/src/fields/field_note.ts +1 -1
- package/src/fields/scratch_field_angle.ts +9 -1
- package/src/fields/scratch_field_variable.ts +9 -8
- package/src/flyout_checkbox_icon.ts +15 -21
- package/src/index.ts +3 -0
- package/src/scratch_block_paster.ts +10 -0
- package/src/{scratch_comment_bubble.js → scratch_comment_bubble.ts} +52 -19
- package/src/scratch_comment_icon.ts +20 -5
- package/src/scratch_continuous_toolbox.ts +16 -0
- package/src/scratch_insertion_marker_previewer.ts +45 -0
- package/src/status_indicator_label_flyout_inflater.ts +4 -4
- package/src/xml.ts +57 -0
- package/CHANGELOG.md +0 -1140
|
@@ -11,17 +11,28 @@ import * as Blockly from "blockly/core";
|
|
|
11
11
|
* @implements {IBubble}
|
|
12
12
|
* @implements {ISelectable}
|
|
13
13
|
*/
|
|
14
|
-
export class ScratchCommentBubble
|
|
15
|
-
|
|
16
|
-
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
25
|
+
flyout: Blockly.IFlyout
|
|
26
26
|
): Blockly.FlyoutItem {
|
|
27
27
|
const label = new StatusIndicatorLabel(
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
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
|
+
}
|