scratch-blocks 2.1.5 → 2.1.7
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/AGENTS.md +58 -14
- package/dist/main.mjs +1 -1
- package/dist/types/src/block_reporting.d.ts.map +1 -1
- package/dist/types/src/blocks/procedures.d.ts +2 -2
- package/dist/types/src/blocks/procedures.d.ts.map +1 -1
- package/dist/types/src/checkable_continuous_flyout.d.ts +6 -3
- package/dist/types/src/checkable_continuous_flyout.d.ts.map +1 -1
- package/dist/types/src/checkbox_bubble.d.ts +7 -7
- package/dist/types/src/checkbox_bubble.d.ts.map +1 -1
- package/dist/types/src/colours.d.ts.map +1 -1
- package/dist/types/src/context_menu_items.d.ts.map +1 -1
- package/dist/types/src/events/events_block_comment_base.d.ts +1 -1
- package/dist/types/src/events/events_block_comment_base.d.ts.map +1 -1
- package/dist/types/src/events/events_block_drag_end.d.ts +1 -1
- package/dist/types/src/events/events_block_drag_end.d.ts.map +1 -1
- package/dist/types/src/events/events_block_drag_outside.d.ts +1 -1
- package/dist/types/src/events/events_block_drag_outside.d.ts.map +1 -1
- package/dist/types/src/fields/field_colour_slider.d.ts.map +1 -1
- package/dist/types/src/fields/field_matrix.d.ts.map +1 -1
- package/dist/types/src/fields/field_note.d.ts +6 -4
- package/dist/types/src/fields/field_note.d.ts.map +1 -1
- package/dist/types/src/fields/field_textinput_removable.d.ts.map +1 -1
- package/dist/types/src/fields/field_variable_getter.d.ts.map +1 -1
- package/dist/types/src/fields/field_vertical_separator.d.ts.map +1 -1
- package/dist/types/src/fields/scratch_field_angle.d.ts.map +1 -1
- package/dist/types/src/fields/scratch_field_dropdown.d.ts.map +1 -1
- package/dist/types/src/fields/scratch_field_number.d.ts.map +1 -1
- package/dist/types/src/fields/scratch_field_variable.d.ts +1 -0
- package/dist/types/src/fields/scratch_field_variable.d.ts.map +1 -1
- package/dist/types/src/flyout_checkbox_icon.d.ts +5 -5
- package/dist/types/src/flyout_checkbox_icon.d.ts.map +1 -1
- package/dist/types/src/glows.d.ts.map +1 -1
- package/dist/types/src/procedures.d.ts +4 -4
- package/dist/types/src/procedures.d.ts.map +1 -1
- package/dist/types/src/recyclable_block_flyout_inflater.d.ts +2 -2
- package/dist/types/src/recyclable_block_flyout_inflater.d.ts.map +1 -1
- package/dist/types/src/renderer/cat/cat_face.d.ts +1 -1
- package/dist/types/src/renderer/cat/cat_face.d.ts.map +1 -1
- package/dist/types/src/renderer/cat/drawer.d.ts.map +1 -1
- package/dist/types/src/renderer/constants.d.ts.map +1 -1
- package/dist/types/src/renderer/drawer.d.ts.map +1 -1
- package/dist/types/src/renderer/render_info.d.ts.map +1 -1
- package/dist/types/src/scratch_blocks_utils.d.ts +22 -0
- package/dist/types/src/scratch_blocks_utils.d.ts.map +1 -1
- package/dist/types/src/scratch_comment_bubble.d.ts +4 -4
- package/dist/types/src/scratch_comment_bubble.d.ts.map +1 -1
- package/dist/types/src/scratch_comment_icon.d.ts +1 -1
- package/dist/types/src/scratch_comment_icon.d.ts.map +1 -1
- package/dist/types/src/scratch_continuous_category.d.ts +3 -1
- package/dist/types/src/scratch_continuous_category.d.ts.map +1 -1
- package/dist/types/src/scratch_continuous_toolbox.d.ts +2 -1
- package/dist/types/src/scratch_continuous_toolbox.d.ts.map +1 -1
- package/dist/types/src/status_indicator_label.d.ts +3 -3
- package/dist/types/src/status_indicator_label.d.ts.map +1 -1
- package/dist/types/src/status_indicator_label_flyout_inflater.d.ts.map +1 -1
- package/dist/types/src/variables.d.ts +1 -1
- package/dist/types/src/variables.d.ts.map +1 -1
- package/dist/types/src/workspace_block_lookup.d.ts +4 -0
- package/dist/types/src/workspace_block_lookup.d.ts.map +1 -0
- package/eslint.config.mjs +21 -28
- package/package.json +1 -1
- package/src/block_reporting.ts +5 -5
- package/src/blocks/control.ts +5 -5
- package/src/blocks/data.ts +1 -1
- package/src/blocks/event.ts +1 -1
- package/src/blocks/motion.ts +2 -2
- package/src/blocks/procedures.ts +162 -69
- package/src/blocks/sensing.ts +0 -1
- package/src/blocks/vertical_extensions.ts +11 -8
- package/src/checkable_continuous_flyout.ts +45 -12
- package/src/checkbox_bubble.ts +7 -7
- package/src/colours.ts +4 -2
- package/src/context_menu_items.ts +41 -16
- package/src/data_category.ts +11 -3
- package/src/events/events_block_comment_base.ts +5 -1
- package/src/events/events_block_comment_change.ts +5 -1
- package/src/events/events_block_comment_collapse.ts +6 -2
- package/src/events/events_block_comment_create.ts +5 -1
- package/src/events/events_block_comment_move.ts +6 -2
- package/src/events/events_block_comment_resize.ts +6 -2
- package/src/events/events_block_drag_end.ts +5 -1
- package/src/events/events_block_drag_outside.ts +5 -1
- package/src/events/events_scratch_variable_create.ts +5 -1
- package/src/fields/field_colour_slider.ts +3 -5
- package/src/fields/field_matrix.ts +33 -17
- package/src/fields/field_note.ts +56 -20
- package/src/fields/field_textinput_removable.ts +13 -4
- package/src/fields/field_variable_getter.ts +20 -6
- package/src/fields/field_vertical_separator.ts +5 -1
- package/src/fields/scratch_field_angle.ts +32 -21
- package/src/fields/scratch_field_dropdown.ts +6 -2
- package/src/fields/scratch_field_number.ts +22 -13
- package/src/fields/scratch_field_variable.ts +26 -12
- package/src/flyout_checkbox_icon.ts +9 -5
- package/src/glows.ts +5 -5
- package/src/index.ts +18 -6
- package/src/procedures.ts +92 -42
- package/src/recyclable_block_flyout_inflater.ts +5 -4
- package/src/renderer/cat/cat_face.ts +1 -1
- package/src/renderer/cat/drawer.ts +4 -1
- package/src/renderer/constants.ts +19 -14
- package/src/renderer/drawer.ts +2 -1
- package/src/renderer/render_info.ts +12 -9
- package/src/renderer/renderer.ts +1 -1
- package/src/scratch_blocks_utils.ts +0 -2
- package/src/scratch_c_block_wrap.ts +37 -21
- package/src/scratch_comment_bubble.ts +30 -19
- package/src/scratch_comment_icon.ts +9 -12
- package/src/scratch_continuous_category.ts +20 -11
- package/src/scratch_continuous_toolbox.ts +12 -3
- package/src/scratch_dragger.ts +2 -2
- package/src/scratch_variable_map.ts +1 -1
- package/src/status_indicator_label.ts +13 -9
- package/src/status_indicator_label_flyout_inflater.ts +2 -1
- package/src/variables.ts +21 -14
- package/src/workspace_block_lookup.ts +14 -0
- package/src/xml.ts +1 -1
- package/types/continuous-toolbox.d.ts +0 -1
|
@@ -28,7 +28,7 @@ Blockly.Connection.getConnectionForOrphanedConnection = function (
|
|
|
28
28
|
// Apply the wrapping logic when the orphaned connection is a statement
|
|
29
29
|
// connection (PREVIOUS_STATEMENT). Value connections (OUTPUT_VALUE) are
|
|
30
30
|
// already handled by the original implementation.
|
|
31
|
-
if (orphanConnection.type === Blockly.ConnectionType.PREVIOUS_STATEMENT) {
|
|
31
|
+
if ((orphanConnection.type as Blockly.ConnectionType) === Blockly.ConnectionType.PREVIOUS_STATEMENT) {
|
|
32
32
|
const checker = orphanConnection.getConnectionChecker()
|
|
33
33
|
for (const input of startBlock.inputList) {
|
|
34
34
|
const conn = input.connection
|
|
@@ -61,32 +61,48 @@ Blockly.Connection.getConnectionForOrphanedConnection = function (
|
|
|
61
61
|
* marker's statement input back to `marker.nextConnection` before the original
|
|
62
62
|
* cleanup runs, so the standard healing logic reconnects B1.next → B2.
|
|
63
63
|
*/
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
const previewerProto = Blockly.InsertionMarkerPreviewer.prototype
|
|
65
|
+
const hideInsertionMarker: unknown = Reflect.get(previewerProto, 'hideInsertionMarker')
|
|
66
|
+
if (typeof hideInsertionMarker !== 'function') {
|
|
67
|
+
throw new Error('Expected InsertionMarkerPreviewer.hideInsertionMarker to be a function')
|
|
68
|
+
}
|
|
69
|
+
const originalHideInsertionMarker = hideInsertionMarker as (
|
|
70
|
+
this: Blockly.InsertionMarkerPreviewer,
|
|
71
|
+
markerConn: Blockly.Connection,
|
|
72
|
+
) => void
|
|
73
|
+
Reflect.set(
|
|
74
|
+
previewerProto,
|
|
75
|
+
'hideInsertionMarker',
|
|
76
|
+
function (this: Blockly.InsertionMarkerPreviewer, markerConn: Blockly.Connection) {
|
|
77
|
+
const marker = markerConn.getSourceBlock()
|
|
78
|
+
const markerPreviousConnection = marker.previousConnection
|
|
79
|
+
const markerNextConnection = marker.nextConnection
|
|
69
80
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
// Restore a real block from a statement input to nextConnection so that
|
|
82
|
+
// unplug(true) can heal the stack correctly. Conditions:
|
|
83
|
+
// - marker is mid-stack (has a predecessor)
|
|
84
|
+
// - marker.nextConnection is empty (displaced block is NOT already there)
|
|
85
|
+
// - a non-marker block is sitting in a statement input
|
|
86
|
+
if (markerPreviousConnection?.isConnected() && markerNextConnection && !markerNextConnection.isConnected()) {
|
|
87
|
+
for (const input of marker.inputList) {
|
|
88
|
+
const conn = input.connection
|
|
89
|
+
const connType = conn?.type as Blockly.ConnectionType | undefined
|
|
90
|
+
if (connType !== Blockly.ConnectionType.NEXT_STATEMENT || !conn?.isConnected()) {
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
const blockInInput = conn.targetBlock()
|
|
80
94
|
if (blockInInput && !blockInInput.isInsertionMarker()) {
|
|
81
95
|
const prev = blockInInput.previousConnection
|
|
82
|
-
if (!prev)
|
|
96
|
+
if (!prev) {
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
83
99
|
prev.disconnect()
|
|
84
|
-
|
|
100
|
+
markerNextConnection.connect(prev)
|
|
85
101
|
break
|
|
86
102
|
}
|
|
87
103
|
}
|
|
88
104
|
}
|
|
89
|
-
}
|
|
90
105
|
|
|
91
|
-
|
|
92
|
-
}
|
|
106
|
+
originalHideInsertionMarker.call(this, markerConn)
|
|
107
|
+
},
|
|
108
|
+
)
|
|
@@ -27,16 +27,16 @@ export class ScratchCommentBubble
|
|
|
27
27
|
this.getSvgRoot().setAttribute('style', `--colour-commentBorder: ${sourceBlock.getColourTertiary()};`)
|
|
28
28
|
this.getSvgRoot().setAttribute('id', this.id)
|
|
29
29
|
|
|
30
|
-
Blockly.browserEvents.conditionalBind(this.getSvgRoot(), 'pointerdown', this, this.startGesture)
|
|
30
|
+
Blockly.browserEvents.conditionalBind(this.getSvgRoot(), 'pointerdown', this, this.startGesture.bind(this))
|
|
31
31
|
// Don't zoom with mousewheel; let it scroll instead.
|
|
32
32
|
Blockly.browserEvents.conditionalBind(this.getSvgRoot(), 'wheel', this, (e: WheelEvent) => {
|
|
33
33
|
e.stopPropagation()
|
|
34
34
|
})
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
setDeleteStyle(
|
|
37
|
+
setDeleteStyle(_enable: boolean) {}
|
|
38
38
|
showContextMenu() {}
|
|
39
|
-
setDragging(
|
|
39
|
+
setDragging(_start: boolean) {}
|
|
40
40
|
select() {}
|
|
41
41
|
unselect() {}
|
|
42
42
|
|
|
@@ -51,10 +51,15 @@ export class ScratchCommentBubble
|
|
|
51
51
|
moveTo(xOrCoordinate: number, y: number): void
|
|
52
52
|
moveTo(xOrCoordinate: Blockly.utils.Coordinate): void
|
|
53
53
|
moveTo(xOrCoordinate: Blockly.utils.Coordinate | number, y?: number) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
let destination: Blockly.utils.Coordinate
|
|
55
|
+
if (xOrCoordinate instanceof Blockly.utils.Coordinate) {
|
|
56
|
+
destination = xOrCoordinate
|
|
57
|
+
} else {
|
|
58
|
+
if (y === undefined) {
|
|
59
|
+
throw new Error('ScratchCommentBubble.moveTo: missing y coordinate')
|
|
60
|
+
}
|
|
61
|
+
destination = new Blockly.utils.Coordinate(xOrCoordinate, y)
|
|
62
|
+
}
|
|
58
63
|
super.moveTo(destination)
|
|
59
64
|
this.redrawAnchorChain()
|
|
60
65
|
}
|
|
@@ -69,14 +74,14 @@ export class ScratchCommentBubble
|
|
|
69
74
|
}
|
|
70
75
|
}
|
|
71
76
|
|
|
72
|
-
startDrag(
|
|
77
|
+
startDrag(_event: PointerEvent) {
|
|
73
78
|
this.dragStartLocation = this.getRelativeToSurfaceXY()
|
|
74
79
|
this.workspace.setResizesEnabled(false)
|
|
75
80
|
this.workspace.getLayerManager()?.moveToDragLayer(this)
|
|
76
81
|
Blockly.utils.dom.addClass(this.getSvgRoot(), 'blocklyDragging')
|
|
77
82
|
}
|
|
78
83
|
|
|
79
|
-
drag(newLocation: Blockly.utils.Coordinate,
|
|
84
|
+
drag(newLocation: Blockly.utils.Coordinate, _event?: PointerEvent) {
|
|
80
85
|
this.moveTo(newLocation)
|
|
81
86
|
}
|
|
82
87
|
|
|
@@ -90,35 +95,41 @@ export class ScratchCommentBubble
|
|
|
90
95
|
}
|
|
91
96
|
|
|
92
97
|
revertDrag() {
|
|
93
|
-
|
|
98
|
+
if (!this.dragStartLocation) {
|
|
99
|
+
throw new Error('ScratchCommentBubble.revertDrag: missing drag start location')
|
|
100
|
+
}
|
|
101
|
+
this.moveTo(this.dragStartLocation)
|
|
94
102
|
}
|
|
95
103
|
|
|
96
104
|
setAnchorLocation(newAnchor: Blockly.utils.Coordinate) {
|
|
97
105
|
const oldAnchor = this.anchor
|
|
98
|
-
const alreadyAnchored =
|
|
106
|
+
const alreadyAnchored = oldAnchor !== undefined
|
|
99
107
|
this.anchor = newAnchor
|
|
100
108
|
if (!alreadyAnchored) {
|
|
101
109
|
this.dropAnchor()
|
|
102
110
|
} else {
|
|
103
111
|
const oldLocation = this.getRelativeToSurfaceXY()
|
|
104
|
-
const delta = Blockly.utils.Coordinate.difference(this.anchor, oldAnchor
|
|
112
|
+
const delta = Blockly.utils.Coordinate.difference(this.anchor, oldAnchor)
|
|
105
113
|
const newLocation = Blockly.utils.Coordinate.sum(oldLocation, delta)
|
|
106
114
|
this.moveTo(newLocation)
|
|
107
115
|
}
|
|
108
116
|
}
|
|
109
117
|
|
|
110
118
|
dropAnchor() {
|
|
119
|
+
if (!this.anchor || !this.sourceBlock) {
|
|
120
|
+
throw new Error('ScratchCommentBubble.dropAnchor: missing anchor or source block')
|
|
121
|
+
}
|
|
111
122
|
const verticalOffset = 16
|
|
112
|
-
this.moveTo(this.anchor
|
|
123
|
+
this.moveTo(this.anchor.x + 40 * (this.workspace.RTL ? -1 : 1), this.anchor.y - verticalOffset)
|
|
113
124
|
const location = this.getRelativeToSurfaceXY()
|
|
114
125
|
this.anchorChain = Blockly.utils.dom.createSvgElement(
|
|
115
126
|
Blockly.utils.Svg.LINE,
|
|
116
127
|
{
|
|
117
|
-
x1: this.anchor
|
|
118
|
-
y1: this.anchor
|
|
128
|
+
x1: this.anchor.x - location.x,
|
|
129
|
+
y1: this.anchor.y - location.y,
|
|
119
130
|
x2: (this.getSize().width / 2) * (this.workspace.RTL ? -1 : 1),
|
|
120
131
|
y2: verticalOffset,
|
|
121
|
-
style: `stroke: ${this.sourceBlock
|
|
132
|
+
style: `stroke: ${this.sourceBlock.getColourTertiary()}; stroke-width: 1`,
|
|
122
133
|
},
|
|
123
134
|
this.getSvgRoot(),
|
|
124
135
|
)
|
|
@@ -126,11 +137,11 @@ export class ScratchCommentBubble
|
|
|
126
137
|
}
|
|
127
138
|
|
|
128
139
|
redrawAnchorChain() {
|
|
129
|
-
if (!this.anchorChain) return
|
|
140
|
+
if (!this.anchorChain || !this.anchor) return
|
|
130
141
|
|
|
131
142
|
const location = this.getRelativeToSurfaceXY()
|
|
132
|
-
this.anchorChain.setAttribute('x1', `${this.anchor
|
|
133
|
-
this.anchorChain.setAttribute('y1', `${this.anchor
|
|
143
|
+
this.anchorChain.setAttribute('x1', `${this.anchor.x - location.x}`)
|
|
144
|
+
this.anchorChain.setAttribute('y1', `${this.anchor.y - location.y}`)
|
|
134
145
|
}
|
|
135
146
|
|
|
136
147
|
getId() {
|
|
@@ -39,7 +39,7 @@ export class ScratchCommentIcon extends Blockly.icons.Icon implements Blockly.IS
|
|
|
39
39
|
return Blockly.icons.IconType.COMMENT
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
initView(
|
|
42
|
+
initView(_pointerDownListener: (e: PointerEvent) => void) {
|
|
43
43
|
// Scratch comments have no indicator icon on the block.
|
|
44
44
|
return
|
|
45
45
|
}
|
|
@@ -57,8 +57,6 @@ export class ScratchCommentIcon extends Blockly.icons.Icon implements Blockly.IS
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
onLocationChange(blockOrigin: Blockly.utils.Coordinate) {
|
|
60
|
-
if (!this.sourceBlock || !this.commentBubble) return
|
|
61
|
-
|
|
62
60
|
if (this.sourceBlock.isInsertionMarker()) {
|
|
63
61
|
this.commentBubble.dispose()
|
|
64
62
|
return
|
|
@@ -74,11 +72,11 @@ export class ScratchCommentIcon extends Blockly.icons.Icon implements Blockly.IS
|
|
|
74
72
|
}
|
|
75
73
|
|
|
76
74
|
setText(text: string) {
|
|
77
|
-
this.commentBubble
|
|
75
|
+
this.commentBubble.setText(text)
|
|
78
76
|
}
|
|
79
77
|
|
|
80
78
|
getText(): string {
|
|
81
|
-
return this.commentBubble
|
|
79
|
+
return this.commentBubble.getText()
|
|
82
80
|
}
|
|
83
81
|
|
|
84
82
|
onTextChanged(oldText: string, newText: string) {
|
|
@@ -97,26 +95,24 @@ export class ScratchCommentIcon extends Blockly.icons.Icon implements Blockly.IS
|
|
|
97
95
|
}
|
|
98
96
|
|
|
99
97
|
setBubbleSize(size: Blockly.utils.Size) {
|
|
100
|
-
this.commentBubble
|
|
98
|
+
this.commentBubble.setSize(size)
|
|
101
99
|
}
|
|
102
100
|
|
|
103
101
|
getBubbleSize(): Blockly.utils.Size {
|
|
104
|
-
return this.commentBubble
|
|
102
|
+
return this.commentBubble.getSize()
|
|
105
103
|
}
|
|
106
104
|
|
|
107
105
|
setBubbleLocation(newLocation: Blockly.utils.Coordinate) {
|
|
108
106
|
const oldLocation = this.getBubbleLocation()
|
|
109
|
-
this.commentBubble
|
|
107
|
+
this.commentBubble.moveTo(newLocation)
|
|
110
108
|
Blockly.Events.fire(new (Blockly.Events.get('block_comment_move'))(this.commentBubble, oldLocation, newLocation))
|
|
111
109
|
}
|
|
112
110
|
|
|
113
111
|
getBubbleLocation(): Blockly.utils.Coordinate {
|
|
114
|
-
return this.commentBubble
|
|
112
|
+
return this.commentBubble.getRelativeToSurfaceXY()
|
|
115
113
|
}
|
|
116
114
|
|
|
117
115
|
saveState(): CommentState | null {
|
|
118
|
-
if (!this.commentBubble) return null
|
|
119
|
-
|
|
120
116
|
const size = this.getBubbleSize()
|
|
121
117
|
const bubbleLocation = this.commentBubble.getRelativeToSurfaceXY()
|
|
122
118
|
const delta = Blockly.utils.Coordinate.difference(bubbleLocation, this.workspaceLocation)
|
|
@@ -145,8 +141,9 @@ export class ScratchCommentIcon extends Blockly.icons.Icon implements Blockly.IS
|
|
|
145
141
|
return true
|
|
146
142
|
}
|
|
147
143
|
|
|
148
|
-
|
|
144
|
+
setBubbleVisible(visible: boolean) {
|
|
149
145
|
this.commentBubble.setCollapsed(!visible)
|
|
146
|
+
return Promise.resolve()
|
|
150
147
|
}
|
|
151
148
|
|
|
152
149
|
getBubble(): ScratchCommentBubble | null {
|
|
@@ -19,6 +19,8 @@ export class ScratchContinuousCategory extends ContinuousCategory {
|
|
|
19
19
|
* devices.
|
|
20
20
|
*/
|
|
21
21
|
private showStatusButton = false
|
|
22
|
+
private iconURI?: string
|
|
23
|
+
private secondaryColour?: string
|
|
22
24
|
|
|
23
25
|
/**
|
|
24
26
|
* Creates a new ScratchContinuousCategory.
|
|
@@ -33,21 +35,29 @@ export class ScratchContinuousCategory extends ContinuousCategory {
|
|
|
33
35
|
) {
|
|
34
36
|
super(toolboxItemDef, parentToolbox, opt_parent)
|
|
35
37
|
this.showStatusButton = toolboxItemDef.showStatusButton === 'true'
|
|
38
|
+
const iconURI = 'iconURI' in toolboxItemDef ? toolboxItemDef.iconURI : undefined
|
|
39
|
+
const secondaryColour = 'secondaryColour' in toolboxItemDef ? toolboxItemDef.secondaryColour : undefined
|
|
40
|
+
this.iconURI = typeof iconURI === 'string' ? iconURI : undefined
|
|
41
|
+
this.secondaryColour = typeof secondaryColour === 'string' ? secondaryColour : undefined
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
/**
|
|
39
45
|
* Creates a DOM element for this category's icon.
|
|
40
46
|
* @returns A DOM element for this category's icon.
|
|
41
47
|
*/
|
|
42
|
-
createIconDom_():
|
|
43
|
-
if (this.
|
|
48
|
+
createIconDom_(): Element {
|
|
49
|
+
if (this.iconURI) {
|
|
44
50
|
const icon = document.createElement('img')
|
|
45
|
-
icon.src = this.
|
|
51
|
+
icon.src = this.iconURI
|
|
46
52
|
icon.className = 'categoryIconBubble'
|
|
47
53
|
return icon
|
|
48
54
|
} else {
|
|
49
55
|
const icon = super.createIconDom_()
|
|
50
|
-
icon
|
|
56
|
+
if (icon instanceof HTMLElement) {
|
|
57
|
+
if (this.secondaryColour) {
|
|
58
|
+
icon.style.border = `1px solid ${this.secondaryColour}`
|
|
59
|
+
}
|
|
60
|
+
}
|
|
51
61
|
return icon
|
|
52
62
|
}
|
|
53
63
|
}
|
|
@@ -59,7 +69,9 @@ export class ScratchContinuousCategory extends ContinuousCategory {
|
|
|
59
69
|
setSelected(isSelected: boolean) {
|
|
60
70
|
super.setSelected(isSelected)
|
|
61
71
|
// Prevent hardcoding the background color to grey.
|
|
62
|
-
this.rowDiv_
|
|
72
|
+
if (this.rowDiv_) {
|
|
73
|
+
this.rowDiv_.style.backgroundColor = ''
|
|
74
|
+
}
|
|
63
75
|
}
|
|
64
76
|
|
|
65
77
|
/**
|
|
@@ -74,10 +86,7 @@ export class ScratchContinuousCategory extends ContinuousCategory {
|
|
|
74
86
|
|
|
75
87
|
/** Registers this toolbox category and unregisters the default one. */
|
|
76
88
|
export function registerScratchContinuousCategory() {
|
|
77
|
-
|
|
78
|
-
Blockly.registry.
|
|
79
|
-
|
|
80
|
-
ScratchContinuousCategory.registrationName,
|
|
81
|
-
ScratchContinuousCategory,
|
|
82
|
-
)
|
|
89
|
+
const registrationName = ScratchContinuousCategory.registrationName
|
|
90
|
+
Blockly.registry.unregister(Blockly.registry.Type.TOOLBOX_ITEM, registrationName)
|
|
91
|
+
Blockly.registry.register(Blockly.registry.Type.TOOLBOX_ITEM, registrationName, ScratchContinuousCategory)
|
|
83
92
|
}
|
|
@@ -16,6 +16,10 @@ export class ScratchContinuousToolbox extends ContinuousToolbox {
|
|
|
16
16
|
*/
|
|
17
17
|
private postRenderCallbacks: (() => void)[] = []
|
|
18
18
|
|
|
19
|
+
private getInitialFlyoutContents_(): Blockly.utils.toolbox.FlyoutItemInfoArray {
|
|
20
|
+
return this.getToolboxItems().flatMap((item) => this.convertToolboxItemToFlyoutItems(item))
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
refreshSelection() {
|
|
20
24
|
// Intentionally a no-op, Scratch manually manages refreshing the toolbox
|
|
21
25
|
// via forceRerender().
|
|
@@ -45,9 +49,14 @@ export class ScratchContinuousToolbox extends ContinuousToolbox {
|
|
|
45
49
|
* Forcibly rerenders the toolbox, preserving selection when possible.
|
|
46
50
|
*/
|
|
47
51
|
forceRerender() {
|
|
48
|
-
const selectedCategoryName = this.
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
const selectedCategoryName = this.getSelectedItem()?.getName()
|
|
53
|
+
if (selectedCategoryName === '') {
|
|
54
|
+
throw new Error('Expected selected category name to be non-empty')
|
|
55
|
+
}
|
|
56
|
+
this.getFlyout().show(this.getInitialFlyoutContents_())
|
|
57
|
+
if (selectedCategoryName) {
|
|
58
|
+
this.selectCategoryByName(selectedCategoryName)
|
|
59
|
+
}
|
|
51
60
|
let callback
|
|
52
61
|
while ((callback = this.postRenderCallbacks.shift())) {
|
|
53
62
|
callback()
|
package/src/scratch_dragger.ts
CHANGED
|
@@ -93,7 +93,7 @@ export class ScratchDragger extends Blockly.dragging.Dragger {
|
|
|
93
93
|
dragRoot.type === 'procedures_definition' &&
|
|
94
94
|
this.wouldDeleteDraggable(event, dragRoot.getRootBlock())
|
|
95
95
|
) {
|
|
96
|
-
const prototype = dragRoot.getInput('custom_block')
|
|
96
|
+
const prototype = dragRoot.getInput('custom_block')?.connection?.targetBlock()
|
|
97
97
|
const hasCaller =
|
|
98
98
|
prototype instanceof Blockly.BlockSvg &&
|
|
99
99
|
isProcedureBlock(prototype) &&
|
|
@@ -118,7 +118,7 @@ export class ScratchDragger extends Blockly.dragging.Dragger {
|
|
|
118
118
|
// on the workspace in order to depict the block mid-drag needs to be
|
|
119
119
|
// deleted.
|
|
120
120
|
if (this.originatedFromFlyout && this.draggedOutOfBounds) {
|
|
121
|
-
Blockly.renderManagement.finishQueuedRenders().then(() => {
|
|
121
|
+
void Blockly.renderManagement.finishQueuedRenders().then(() => {
|
|
122
122
|
const rootBlock = this.getDragRoot(this.draggable)
|
|
123
123
|
if (rootBlock instanceof Blockly.BlockSvg) {
|
|
124
124
|
rootBlock.dispose()
|
|
@@ -12,7 +12,7 @@ class ScratchVariableMap extends Blockly.VariableMap {
|
|
|
12
12
|
// Variable names in Blockly are case-insensitive, but case sensitive in
|
|
13
13
|
// Scratch. Override the implementation to only return a variable whose name
|
|
14
14
|
// is identical to the one requested.
|
|
15
|
-
const variables = this.getVariablesOfType(type
|
|
15
|
+
const variables = this.getVariablesOfType(type)
|
|
16
16
|
if (!variables.length) return null
|
|
17
17
|
return variables.find((v) => v.getName() === name) ?? null
|
|
18
18
|
}
|
|
@@ -46,7 +46,7 @@ export class StatusIndicatorLabel extends Blockly.FlyoutButton {
|
|
|
46
46
|
/**
|
|
47
47
|
* Function to be invoked when the status indicator is clicked.
|
|
48
48
|
*/
|
|
49
|
-
static statusButtonCallback: (extensionId: string) => void
|
|
49
|
+
static statusButtonCallback: ((extensionId: string) => void) | null = null
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Creates a new StatusIndicatorLabel.
|
|
@@ -60,11 +60,17 @@ export class StatusIndicatorLabel extends Blockly.FlyoutButton {
|
|
|
60
60
|
json: Blockly.utils.toolbox.LabelInfo,
|
|
61
61
|
) {
|
|
62
62
|
super(workspace, targetWorkspace, json, true)
|
|
63
|
-
|
|
63
|
+
if (!json.id) {
|
|
64
|
+
throw new Error('StatusIndicatorLabel: missing required extension id in toolbox JSON')
|
|
65
|
+
}
|
|
66
|
+
this.extensionId = json.id
|
|
64
67
|
|
|
65
68
|
const heightDelta = 40 - this.height
|
|
66
69
|
this.height = 40
|
|
67
|
-
const text = this.getSvgRoot().querySelector('text')
|
|
70
|
+
const text = this.getSvgRoot().querySelector('text')
|
|
71
|
+
if (!text) {
|
|
72
|
+
throw new Error('StatusIndicatorLabel: missing flyout text element')
|
|
73
|
+
}
|
|
68
74
|
const previousY = Number(text.getAttribute('y'))
|
|
69
75
|
|
|
70
76
|
text.setAttribute('y', `${previousY + heightDelta / 2}`)
|
|
@@ -73,7 +79,7 @@ export class StatusIndicatorLabel extends Blockly.FlyoutButton {
|
|
|
73
79
|
const marginX = 20
|
|
74
80
|
const marginY = 5
|
|
75
81
|
const touchPadding = 16
|
|
76
|
-
const flyoutWidth = targetWorkspace.getFlyout()
|
|
82
|
+
const flyoutWidth = targetWorkspace.getFlyout()?.getWidth() ?? 0
|
|
77
83
|
|
|
78
84
|
const statusButtonX = workspace.RTL
|
|
79
85
|
? marginX - flyoutWidth + statusButtonWidth
|
|
@@ -129,17 +135,15 @@ export class StatusIndicatorLabel extends Blockly.FlyoutButton {
|
|
|
129
135
|
* @package
|
|
130
136
|
*/
|
|
131
137
|
setImageSrc(src: string) {
|
|
132
|
-
|
|
133
|
-
this.imageElement.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', src)
|
|
134
|
-
}
|
|
138
|
+
this.imageElement.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', src)
|
|
135
139
|
}
|
|
136
140
|
|
|
137
141
|
/**
|
|
138
142
|
* Gets the extension state. Overridden externally.
|
|
139
|
-
* @param
|
|
143
|
+
* @param _extensionId A string identifying which extension's state to retrieve.
|
|
140
144
|
* @returns Whether the extension is ready to be used.
|
|
141
145
|
*/
|
|
142
|
-
getExtensionState(
|
|
146
|
+
getExtensionState(_extensionId: string): StatusButtonState {
|
|
143
147
|
return StatusButtonState.NOT_READY
|
|
144
148
|
}
|
|
145
149
|
|
|
@@ -18,7 +18,8 @@ class StatusIndicatorLabelFlyoutInflater extends Blockly.LabelFlyoutInflater {
|
|
|
18
18
|
* @returns The newly created status indicator label.
|
|
19
19
|
*/
|
|
20
20
|
load(state: Blockly.utils.toolbox.LabelInfo, flyout: Blockly.IFlyout): Blockly.FlyoutItem {
|
|
21
|
-
const
|
|
21
|
+
const targetWorkspace = flyout.targetWorkspace ?? flyout.getWorkspace()
|
|
22
|
+
const label = new StatusIndicatorLabel(flyout.getWorkspace(), targetWorkspace, state)
|
|
22
23
|
label.show()
|
|
23
24
|
return new Blockly.FlyoutItem(label, STATUS_INDICATOR_LABEL_TYPE)
|
|
24
25
|
}
|
package/src/variables.ts
CHANGED
|
@@ -84,23 +84,26 @@ export function createVariable(
|
|
|
84
84
|
// opt_type -- turns a falsey opt_type into ''
|
|
85
85
|
// TODO (#1251) Warn developers that they didn't provide an opt_type/
|
|
86
86
|
// provided a falsey opt_type
|
|
87
|
-
opt_type = opt_type
|
|
87
|
+
opt_type = opt_type ?? ''
|
|
88
88
|
newMsg = Blockly.Msg.NEW_VARIABLE_TITLE
|
|
89
89
|
modalTitle = Blockly.Msg.VARIABLE_MODAL_TITLE
|
|
90
90
|
}
|
|
91
91
|
const validate = nameValidator.bind(null, opt_type)
|
|
92
|
+
if (!prompt) {
|
|
93
|
+
throw new Error('createVariable: prompt handler is not set')
|
|
94
|
+
}
|
|
92
95
|
|
|
93
96
|
// Prompt the user to enter a name for the variable
|
|
94
|
-
prompt
|
|
97
|
+
prompt(
|
|
95
98
|
newMsg,
|
|
96
99
|
'',
|
|
97
|
-
(text: string, additionalVars
|
|
98
|
-
variableOptions = variableOptions
|
|
100
|
+
(text: string, additionalVars?: string[], variableOptions?: { scope?: string; isCloud?: boolean }) => {
|
|
101
|
+
variableOptions = variableOptions ?? {}
|
|
99
102
|
const scope = variableOptions.scope
|
|
100
|
-
const isLocal = scope === 'local'
|
|
101
|
-
const isCloud = variableOptions.isCloud
|
|
103
|
+
const isLocal = scope === 'local'
|
|
104
|
+
const isCloud = variableOptions.isCloud ?? false
|
|
102
105
|
// Default to [] if additionalVars is not provided
|
|
103
|
-
additionalVars = additionalVars
|
|
106
|
+
additionalVars = additionalVars ?? []
|
|
104
107
|
// Only use additionalVars for global variable creation.
|
|
105
108
|
const additionalVarNames = isLocal ? [] : additionalVars
|
|
106
109
|
|
|
@@ -159,7 +162,7 @@ export function createVariable(
|
|
|
159
162
|
function nameValidator(
|
|
160
163
|
type: string,
|
|
161
164
|
text: string,
|
|
162
|
-
workspace: Blockly.
|
|
165
|
+
workspace: Blockly.Workspace,
|
|
163
166
|
additionalVars: string[],
|
|
164
167
|
isCloud: boolean,
|
|
165
168
|
opt_callback?: (id?: string) => void,
|
|
@@ -200,7 +203,7 @@ function nameValidator(
|
|
|
200
203
|
*/
|
|
201
204
|
function validateBroadcastMessageName(
|
|
202
205
|
name: string,
|
|
203
|
-
workspace: Blockly.
|
|
206
|
+
workspace: Blockly.Workspace,
|
|
204
207
|
opt_callback?: (id?: string) => void,
|
|
205
208
|
): string | null {
|
|
206
209
|
if (!name) {
|
|
@@ -242,7 +245,7 @@ function validateBroadcastMessageName(
|
|
|
242
245
|
*/
|
|
243
246
|
function validateScalarVarOrListName(
|
|
244
247
|
name: string,
|
|
245
|
-
workspace: Blockly.
|
|
248
|
+
workspace: Blockly.Workspace,
|
|
246
249
|
additionalVars: string[],
|
|
247
250
|
isCloud: boolean,
|
|
248
251
|
type: string,
|
|
@@ -275,7 +278,7 @@ function validateScalarVarOrListName(
|
|
|
275
278
|
* an existing variable was chosen.
|
|
276
279
|
*/
|
|
277
280
|
export function renameVariable(
|
|
278
|
-
workspace: Blockly.
|
|
281
|
+
workspace: Blockly.Workspace,
|
|
279
282
|
variable: ScratchVariableModel,
|
|
280
283
|
opt_callback?: (id?: string) => void,
|
|
281
284
|
) {
|
|
@@ -305,15 +308,19 @@ export function renameVariable(
|
|
|
305
308
|
promptDefaultText = promptDefaultText.substring(CLOUD_PREFIX.length)
|
|
306
309
|
}
|
|
307
310
|
|
|
308
|
-
prompt
|
|
311
|
+
if (!prompt) {
|
|
312
|
+
throw new Error('renameVariable: prompt handler is not set')
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
prompt(
|
|
309
316
|
promptText,
|
|
310
317
|
promptDefaultText,
|
|
311
|
-
(newName: string, additionalVars
|
|
318
|
+
(newName: string, additionalVars?: string[]) => {
|
|
312
319
|
if (variable.isCloud && newName.length > 0 && newName.startsWith(CLOUD_PREFIX)) {
|
|
313
320
|
newName = newName.substring(CLOUD_PREFIX.length)
|
|
314
321
|
// The name validator will add the prefix back
|
|
315
322
|
}
|
|
316
|
-
additionalVars = additionalVars
|
|
323
|
+
additionalVars = additionalVars ?? []
|
|
317
324
|
const additionalVarNames = variable.isLocal ? [] : additionalVars
|
|
318
325
|
const validatedText = validate(newName, workspace, additionalVarNames, variable.isCloud)
|
|
319
326
|
if (validatedText) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as Blockly from 'blockly/core'
|
|
2
|
+
|
|
3
|
+
export function getRequiredMainWorkspaceSvg(): Blockly.WorkspaceSvg {
|
|
4
|
+
const mainWorkspace = Blockly.getMainWorkspace()
|
|
5
|
+
if (!(mainWorkspace instanceof Blockly.WorkspaceSvg)) {
|
|
6
|
+
throw new Error('Expected main workspace to be a WorkspaceSvg')
|
|
7
|
+
}
|
|
8
|
+
return mainWorkspace
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getBlockSvgById(workspace: Blockly.WorkspaceSvg, id: string): Blockly.BlockSvg | null {
|
|
12
|
+
const block = workspace.getBlockById(id)
|
|
13
|
+
return block instanceof Blockly.BlockSvg ? block : null
|
|
14
|
+
}
|
package/src/xml.ts
CHANGED
|
@@ -22,7 +22,7 @@ export function clearWorkspaceAndLoadFromXml(xml: Element, workspace: Blockly.Wo
|
|
|
22
22
|
const id = variable.getAttribute('id')
|
|
23
23
|
if (!id) continue
|
|
24
24
|
const type = variable.getAttribute('type') ?? ''
|
|
25
|
-
const name = variable.textContent
|
|
25
|
+
const name = variable.textContent
|
|
26
26
|
const isLocal = variable.getAttribute('islocal') === 'true'
|
|
27
27
|
const isCloud = variable.getAttribute('iscloud') === 'true'
|
|
28
28
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
declare module '@blockly/continuous-toolbox'
|