scratch-blocks 2.1.4 → 2.1.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/AGENTS.md +76 -24
- package/README.md +40 -0
- 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/index.d.ts +1 -0
- package/dist/types/src/index.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_c_block_wrap.d.ts +2 -0
- package/dist/types/src/scratch_c_block_wrap.d.ts.map +1 -0
- 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 +23 -26
- package/package.json +10 -3
- package/src/block_reporting.ts +5 -5
- package/src/blocks/control.ts +5 -5
- 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 +21 -11
- 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 +108 -0
- package/src/scratch_comment_bubble.ts +30 -19
- package/src/scratch_comment_icon.ts +9 -12
- package/src/scratch_connection_checker.ts +1 -2
- 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/tsconfig.build.json +4 -0
- package/tsconfig.json +1 -1
- package/vitest.config.ts +35 -0
- package/dist/types/tests/blocks/logic_ternary_test.d.ts +0 -13
- package/dist/types/tests/blocks/logic_ternary_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/block_test.d.ts +0 -4
- package/dist/types/tests/jsunit/block_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/connection_db_test.d.ts +0 -25
- package/dist/types/tests/jsunit/connection_db_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/connection_test.d.ts +0 -39
- package/dist/types/tests/jsunit/connection_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/db_test.d.ts +0 -7
- package/dist/types/tests/jsunit/db_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/event_test.d.ts +0 -76
- package/dist/types/tests/jsunit/event_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/extensions_test.d.ts +0 -18
- package/dist/types/tests/jsunit/extensions_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/field_angle_test.d.ts +0 -3
- package/dist/types/tests/jsunit/field_angle_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/field_number_test.d.ts +0 -3
- package/dist/types/tests/jsunit/field_number_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/field_test.d.ts +0 -8
- package/dist/types/tests/jsunit/field_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/field_variable_getter_test.d.ts +0 -5
- package/dist/types/tests/jsunit/field_variable_getter_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/field_variable_test.d.ts +0 -19
- package/dist/types/tests/jsunit/field_variable_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/generator_test.d.ts +0 -2
- package/dist/types/tests/jsunit/generator_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/gesture_test.d.ts +0 -10
- package/dist/types/tests/jsunit/gesture_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/input_test.d.ts +0 -9
- package/dist/types/tests/jsunit/input_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/json_test.d.ts +0 -11
- package/dist/types/tests/jsunit/json_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/names_test.d.ts +0 -5
- package/dist/types/tests/jsunit/names_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/procedure_test.d.ts +0 -15
- package/dist/types/tests/jsunit/procedure_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/scratch_block_comment_test.d.ts +0 -14
- package/dist/types/tests/jsunit/scratch_block_comment_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/svg_test.d.ts +0 -14
- package/dist/types/tests/jsunit/svg_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/test_runner.d.ts +0 -2
- package/dist/types/tests/jsunit/test_runner.d.ts.map +0 -1
- package/dist/types/tests/jsunit/test_utilities.d.ts +0 -50
- package/dist/types/tests/jsunit/test_utilities.d.ts.map +0 -1
- package/dist/types/tests/jsunit/utils_test.d.ts +0 -10
- package/dist/types/tests/jsunit/utils_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/variable_map_test.d.ts +0 -28
- package/dist/types/tests/jsunit/variable_map_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/variable_model_test.d.ts +0 -14
- package/dist/types/tests/jsunit/variable_model_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/widget_div_test.d.ts +0 -37
- package/dist/types/tests/jsunit/widget_div_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/workspace_comment_test.d.ts +0 -13
- package/dist/types/tests/jsunit/workspace_comment_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/workspace_test.d.ts +0 -22
- package/dist/types/tests/jsunit/workspace_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/workspace_undo_redo_test.d.ts +0 -33
- package/dist/types/tests/jsunit/workspace_undo_redo_test.d.ts.map +0 -1
- package/dist/types/tests/jsunit/xml_test.d.ts +0 -55
- package/dist/types/tests/jsunit/xml_test.d.ts.map +0 -1
- package/dist/types/tests/workspace_svg/workspace_svg_test.d.ts +0 -12
- package/dist/types/tests/workspace_svg/workspace_svg_test.d.ts.map +0 -1
- package/types/continuous-toolbox.d.ts +0 -1
package/src/blocks/procedures.ts
CHANGED
|
@@ -29,8 +29,8 @@ import type { ScratchDragger } from '../scratch_dragger'
|
|
|
29
29
|
type ConnectionMap = Record<
|
|
30
30
|
string,
|
|
31
31
|
{
|
|
32
|
-
shadow: Element
|
|
33
|
-
block: Blockly.BlockSvg
|
|
32
|
+
shadow: Element | undefined
|
|
33
|
+
block: Blockly.BlockSvg | null
|
|
34
34
|
} | null
|
|
35
35
|
>
|
|
36
36
|
|
|
@@ -43,6 +43,86 @@ enum ArgumentType {
|
|
|
43
43
|
BOOLEAN = 'b',
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Parse a serialized procedure argument type token.
|
|
48
|
+
* @param value Serialized token from procCode.
|
|
49
|
+
* @returns The matching argument type.
|
|
50
|
+
*/
|
|
51
|
+
function parseArgumentType(value: string): ArgumentType {
|
|
52
|
+
switch (value) {
|
|
53
|
+
case 'n':
|
|
54
|
+
return ArgumentType.NUMBER
|
|
55
|
+
case 'b':
|
|
56
|
+
return ArgumentType.BOOLEAN
|
|
57
|
+
case 's':
|
|
58
|
+
return ArgumentType.STRING
|
|
59
|
+
default:
|
|
60
|
+
throw new Error(`Found a custom procedure with an invalid type: ${value}`)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Read a required mutation attribute or throw if missing.
|
|
66
|
+
* @param xmlElement The mutation element that should contain required attributes.
|
|
67
|
+
* @param name The specific mutation attribute to retrieve.
|
|
68
|
+
* @returns Attribute value.
|
|
69
|
+
*/
|
|
70
|
+
function getRequiredMutationAttribute(xmlElement: Element, name: string): string {
|
|
71
|
+
const value = xmlElement.getAttribute(name)
|
|
72
|
+
if (value === null) {
|
|
73
|
+
throw new Error(`Missing required mutation attribute: ${name}`)
|
|
74
|
+
}
|
|
75
|
+
return value
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Parse a required mutation attribute as JSON, then validate its type.
|
|
80
|
+
* @param xmlElement The mutation element that should contain required attributes.
|
|
81
|
+
* @param name The specific mutation attribute to retrieve and parse.
|
|
82
|
+
* @param parse Validates and narrows the parsed JSON value.
|
|
83
|
+
* @returns Parsed and validated mutation attribute value.
|
|
84
|
+
*/
|
|
85
|
+
function parseRequiredMutationJson<T>(
|
|
86
|
+
xmlElement: Element,
|
|
87
|
+
name: string,
|
|
88
|
+
parse: (value: unknown, name: string) => T,
|
|
89
|
+
): T {
|
|
90
|
+
const rawValue = getRequiredMutationAttribute(xmlElement, name)
|
|
91
|
+
let parsedValue: unknown
|
|
92
|
+
try {
|
|
93
|
+
parsedValue = JSON.parse(rawValue)
|
|
94
|
+
} catch {
|
|
95
|
+
throw new Error(`Invalid JSON in mutation attribute: ${name}`)
|
|
96
|
+
}
|
|
97
|
+
return parse(parsedValue, name)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Validate a parsed mutation value as a boolean.
|
|
102
|
+
* @param value Parsed mutation value.
|
|
103
|
+
* @param name Attribute name used in error messages.
|
|
104
|
+
* @returns Validated boolean value.
|
|
105
|
+
*/
|
|
106
|
+
function parseBooleanMutationValue(value: unknown, name: string): boolean {
|
|
107
|
+
if (typeof value !== 'boolean') {
|
|
108
|
+
throw new Error(`Expected boolean JSON value in mutation attribute: ${name}`)
|
|
109
|
+
}
|
|
110
|
+
return value
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Validate a parsed mutation value as a string array.
|
|
115
|
+
* @param value Parsed mutation value.
|
|
116
|
+
* @param name Attribute name used in error messages.
|
|
117
|
+
* @returns Validated string array value.
|
|
118
|
+
*/
|
|
119
|
+
function parseStringArrayMutationValue(value: unknown, name: string): string[] {
|
|
120
|
+
if (!Array.isArray(value) || !value.every((entry) => typeof entry === 'string')) {
|
|
121
|
+
throw new Error(`Expected string[] JSON value in mutation attribute: ${name}`)
|
|
122
|
+
}
|
|
123
|
+
return value
|
|
124
|
+
}
|
|
125
|
+
|
|
46
126
|
/**
|
|
47
127
|
* A drag strategy for the procedures_prototype block that delegates all drag
|
|
48
128
|
* operations to its parent (the procedures_definition block). This lets the
|
|
@@ -193,10 +273,10 @@ function callerMutationToDom(this: ProcedureCallBlock): Element {
|
|
|
193
273
|
* @param xmlElement XML storage element.
|
|
194
274
|
*/
|
|
195
275
|
function callerDomToMutation(this: ProcedureCallBlock, xmlElement: Element) {
|
|
196
|
-
this.procCode_ = xmlElement
|
|
197
|
-
this.generateShadows_ =
|
|
198
|
-
this.argumentIds_ =
|
|
199
|
-
this.warp_ =
|
|
276
|
+
this.procCode_ = getRequiredMutationAttribute(xmlElement, 'proccode')
|
|
277
|
+
this.generateShadows_ = parseRequiredMutationJson(xmlElement, 'generateshadows', parseBooleanMutationValue)
|
|
278
|
+
this.argumentIds_ = parseRequiredMutationJson(xmlElement, 'argumentids', parseStringArrayMutationValue)
|
|
279
|
+
this.warp_ = parseRequiredMutationJson(xmlElement, 'warp', parseBooleanMutationValue)
|
|
200
280
|
this.updateDisplay_()
|
|
201
281
|
}
|
|
202
282
|
|
|
@@ -230,15 +310,15 @@ function definitionMutationToDom(
|
|
|
230
310
|
* @param xmlElement XML storage element.
|
|
231
311
|
*/
|
|
232
312
|
function definitionDomToMutation(this: ProcedurePrototypeBlock | ProcedureDeclarationBlock, xmlElement: Element) {
|
|
233
|
-
this.procCode_ = xmlElement
|
|
234
|
-
this.warp_ =
|
|
313
|
+
this.procCode_ = getRequiredMutationAttribute(xmlElement, 'proccode')
|
|
314
|
+
this.warp_ = parseRequiredMutationJson(xmlElement, 'warp', parseBooleanMutationValue)
|
|
235
315
|
|
|
236
316
|
const prevArgIds = this.argumentIds_
|
|
237
317
|
const prevDisplayNames = this.displayNames_
|
|
238
318
|
|
|
239
|
-
this.argumentIds_ =
|
|
240
|
-
this.displayNames_ =
|
|
241
|
-
this.argumentDefaults_ =
|
|
319
|
+
this.argumentIds_ = parseRequiredMutationJson(xmlElement, 'argumentids', parseStringArrayMutationValue)
|
|
320
|
+
this.displayNames_ = parseRequiredMutationJson(xmlElement, 'argumentnames', parseStringArrayMutationValue)
|
|
321
|
+
this.argumentDefaults_ = parseRequiredMutationJson(xmlElement, 'argumentdefaults', parseStringArrayMutationValue)
|
|
242
322
|
|
|
243
323
|
// During full XML deserialization (Blockly.Xml.domToWorkspace), the mutation element
|
|
244
324
|
// is part of the parsed XML tree and its parent element also contains <value> children
|
|
@@ -308,13 +388,11 @@ function disconnectOldBlocks_(this: ProcedureBlock): ConnectionMap {
|
|
|
308
388
|
const connectionMap: ConnectionMap = {}
|
|
309
389
|
for (const input of this.inputList) {
|
|
310
390
|
if (input.connection) {
|
|
311
|
-
const target = input.connection.targetBlock()
|
|
312
|
-
|
|
313
|
-
shadow: input.connection.getShadowDom(true)
|
|
314
|
-
block: target,
|
|
391
|
+
const target = input.connection.targetBlock()
|
|
392
|
+
connectionMap[input.name] = {
|
|
393
|
+
shadow: input.connection.getShadowDom(true) ?? undefined,
|
|
394
|
+
block: target as Blockly.BlockSvg | null,
|
|
315
395
|
}
|
|
316
|
-
connectionMap[input.name] = saveInfo
|
|
317
|
-
|
|
318
396
|
if (target) {
|
|
319
397
|
input.connection.disconnect()
|
|
320
398
|
}
|
|
@@ -349,16 +427,7 @@ function createAllInputs_(this: ProcedureBlock, connectionMap: ConnectionMap) {
|
|
|
349
427
|
for (const component of procComponents) {
|
|
350
428
|
let labelText
|
|
351
429
|
if (component.startsWith('%')) {
|
|
352
|
-
const argumentType = component.substring(1, 2)
|
|
353
|
-
if (
|
|
354
|
-
!(
|
|
355
|
-
argumentType === ArgumentType.NUMBER ||
|
|
356
|
-
argumentType === ArgumentType.BOOLEAN ||
|
|
357
|
-
argumentType === ArgumentType.STRING
|
|
358
|
-
)
|
|
359
|
-
) {
|
|
360
|
-
throw new Error('Found an custom procedure with an invalid type: ' + argumentType)
|
|
361
|
-
}
|
|
430
|
+
const argumentType = parseArgumentType(component.substring(1, 2))
|
|
362
431
|
labelText = component.substring(2).trim()
|
|
363
432
|
|
|
364
433
|
const id = this.argumentIds_[argumentCount]
|
|
@@ -386,19 +455,20 @@ function createAllInputs_(this: ProcedureBlock, connectionMap: ConnectionMap) {
|
|
|
386
455
|
* connected to those IDs at the beginning of the mutation.
|
|
387
456
|
*/
|
|
388
457
|
function disposeObsoleteBlocks_(this: ProcedureBlock, connectionMap: ConnectionMap) {
|
|
389
|
-
for (const id
|
|
390
|
-
const
|
|
391
|
-
if (
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
458
|
+
for (const [id, saveInfo] of Object.entries(connectionMap)) {
|
|
459
|
+
const block = saveInfo?.block
|
|
460
|
+
if (!block) {
|
|
461
|
+
continue
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const isOrphanedArgumentReporter =
|
|
465
|
+
this.type === 'procedures_prototype' &&
|
|
466
|
+
(block.type === 'argument_reporter_string_number' || block.type === 'argument_reporter_boolean')
|
|
467
|
+
if (block.isShadow() || isOrphanedArgumentReporter) {
|
|
468
|
+
block.dispose()
|
|
469
|
+
connectionMap[id] = null
|
|
470
|
+
// At this point we know which shadow DOMs are about to be orphaned in
|
|
471
|
+
// the VM. What do we do with that information?
|
|
402
472
|
}
|
|
403
473
|
}
|
|
404
474
|
}
|
|
@@ -458,6 +528,9 @@ function buildShadowDom_(type: ArgumentType): Element {
|
|
|
458
528
|
*/
|
|
459
529
|
function attachShadow_(this: ProcedureCallBlock, input: Blockly.Input, argumentType: ArgumentType) {
|
|
460
530
|
if (argumentType === ArgumentType.NUMBER || argumentType === ArgumentType.STRING) {
|
|
531
|
+
if (!input.connection) {
|
|
532
|
+
throw new Error(`Expected input connection for argument ${String(argumentType)}`)
|
|
533
|
+
}
|
|
461
534
|
const blockType = argumentType === ArgumentType.NUMBER ? 'math_number' : 'text'
|
|
462
535
|
Blockly.Events.disable()
|
|
463
536
|
let newBlock
|
|
@@ -479,7 +552,7 @@ function attachShadow_(this: ProcedureCallBlock, input: Blockly.Input, argumentT
|
|
|
479
552
|
if (Blockly.Events.isEnabled()) {
|
|
480
553
|
Blockly.Events.fire(new (Blockly.Events.get(Blockly.Events.BLOCK_CREATE))(newBlock))
|
|
481
554
|
}
|
|
482
|
-
newBlock.outputConnection.connect(input.connection
|
|
555
|
+
newBlock.outputConnection.connect(input.connection)
|
|
483
556
|
}
|
|
484
557
|
}
|
|
485
558
|
|
|
@@ -516,7 +589,7 @@ function createArgumentReporter_(
|
|
|
516
589
|
Blockly.Events.enable()
|
|
517
590
|
}
|
|
518
591
|
if (Blockly.Events.isEnabled()) {
|
|
519
|
-
Blockly.Events.fire(new (Blockly.Events.get(Blockly.Events.BLOCK_CREATE))(newBlock))
|
|
592
|
+
void Blockly.Events.fire(new (Blockly.Events.get(Blockly.Events.BLOCK_CREATE))(newBlock))
|
|
520
593
|
}
|
|
521
594
|
return newBlock
|
|
522
595
|
}
|
|
@@ -538,21 +611,25 @@ function populateArgumentOnCaller_(
|
|
|
538
611
|
id: string,
|
|
539
612
|
input: Blockly.Input,
|
|
540
613
|
) {
|
|
541
|
-
let oldBlock: Blockly.BlockSvg | undefined
|
|
614
|
+
let oldBlock: Blockly.BlockSvg | null | undefined
|
|
542
615
|
let oldShadow: Element | undefined
|
|
543
|
-
if (
|
|
616
|
+
if (id in connectionMap) {
|
|
544
617
|
const saveInfo = connectionMap[id]
|
|
545
618
|
oldBlock = saveInfo?.block
|
|
546
619
|
oldShadow = saveInfo?.shadow
|
|
547
620
|
}
|
|
548
621
|
|
|
549
|
-
|
|
622
|
+
const conn = input.connection
|
|
623
|
+
if (!conn) {
|
|
624
|
+
throw new Error(`Expected caller input connection for argument id ${id}`)
|
|
625
|
+
}
|
|
626
|
+
if (oldBlock) {
|
|
550
627
|
// Reattach the old block and shadow DOM.
|
|
551
628
|
connectionMap[input.name] = null
|
|
552
|
-
oldBlock.outputConnection.connect(
|
|
629
|
+
oldBlock.outputConnection.connect(conn)
|
|
553
630
|
if (type !== ArgumentType.BOOLEAN && this.generateShadows_) {
|
|
554
|
-
const shadowDom = oldShadow
|
|
555
|
-
|
|
631
|
+
const shadowDom = oldShadow ?? this.buildShadowDom_(type)
|
|
632
|
+
conn.setShadowDom(shadowDom)
|
|
556
633
|
}
|
|
557
634
|
} else if (this.generateShadows_) {
|
|
558
635
|
this.attachShadow_(input, type)
|
|
@@ -584,7 +661,12 @@ function populateArgumentOnPrototype_(
|
|
|
584
661
|
}
|
|
585
662
|
|
|
586
663
|
let oldBlock: Blockly.BlockSvg | null = null
|
|
587
|
-
|
|
664
|
+
const conn = input.connection
|
|
665
|
+
if (!conn) {
|
|
666
|
+
throw new Error(`Expected prototype input connection for argument id ${id}`)
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (id in connectionMap) {
|
|
588
670
|
const saveInfo = connectionMap[id]
|
|
589
671
|
oldBlock = saveInfo?.block ?? null
|
|
590
672
|
}
|
|
@@ -594,7 +676,7 @@ function populateArgumentOnPrototype_(
|
|
|
594
676
|
|
|
595
677
|
// Decide which block to attach.
|
|
596
678
|
let argumentReporter: Blockly.BlockSvg
|
|
597
|
-
if (
|
|
679
|
+
if (oldBlock && oldTypeMatches) {
|
|
598
680
|
// Update the text if needed. The old argument reporter is the same type,
|
|
599
681
|
// and on the same input, but the argument's display name may have changed.
|
|
600
682
|
argumentReporter = oldBlock
|
|
@@ -605,7 +687,7 @@ function populateArgumentOnPrototype_(
|
|
|
605
687
|
}
|
|
606
688
|
|
|
607
689
|
// Attach the block.
|
|
608
|
-
|
|
690
|
+
conn.connect(argumentReporter.outputConnection)
|
|
609
691
|
}
|
|
610
692
|
|
|
611
693
|
/**
|
|
@@ -627,13 +709,17 @@ function populateArgumentOnDeclaration_(
|
|
|
627
709
|
input: Blockly.Input,
|
|
628
710
|
) {
|
|
629
711
|
let oldBlock: Blockly.BlockSvg | null = null
|
|
630
|
-
if (
|
|
712
|
+
if (id in connectionMap) {
|
|
631
713
|
const saveInfo = connectionMap[id]
|
|
632
714
|
oldBlock = saveInfo?.block ?? null
|
|
633
715
|
}
|
|
634
716
|
|
|
635
717
|
const oldTypeMatches = checkOldEditorTypeMatches_(oldBlock, type)
|
|
636
718
|
const displayName = this.displayNames_[index]
|
|
719
|
+
const conn = input.connection
|
|
720
|
+
if (!conn) {
|
|
721
|
+
throw new Error(`Expected declaration input connection for argument id ${id}`)
|
|
722
|
+
}
|
|
637
723
|
|
|
638
724
|
// Decide which block to attach.
|
|
639
725
|
let argumentEditor: Blockly.BlockSvg
|
|
@@ -646,7 +732,7 @@ function populateArgumentOnDeclaration_(
|
|
|
646
732
|
}
|
|
647
733
|
|
|
648
734
|
// Attach the block.
|
|
649
|
-
|
|
735
|
+
conn.connect(argumentEditor.outputConnection)
|
|
650
736
|
}
|
|
651
737
|
|
|
652
738
|
/**
|
|
@@ -721,13 +807,13 @@ function createArgumentEditor_(
|
|
|
721
807
|
newBlock.setShadow(true)
|
|
722
808
|
if (!this.isInsertionMarker()) {
|
|
723
809
|
newBlock.initSvg()
|
|
724
|
-
newBlock.queueRender()
|
|
810
|
+
void newBlock.queueRender()
|
|
725
811
|
}
|
|
726
812
|
} finally {
|
|
727
813
|
Blockly.Events.enable()
|
|
728
814
|
}
|
|
729
815
|
if (Blockly.Events.isEnabled()) {
|
|
730
|
-
Blockly.Events.fire(new (Blockly.Events.get(Blockly.Events.BLOCK_CREATE))(newBlock))
|
|
816
|
+
void Blockly.Events.fire(new (Blockly.Events.get(Blockly.Events.BLOCK_CREATE))(newBlock))
|
|
731
817
|
}
|
|
732
818
|
return newBlock
|
|
733
819
|
}
|
|
@@ -749,8 +835,11 @@ function updateDeclarationProcCode_(this: ProcedureDeclarationBlock) {
|
|
|
749
835
|
this.procCode_ += input.fieldRow[0].getValue()
|
|
750
836
|
} else if (input.type === Blockly.inputs.inputTypes.VALUE) {
|
|
751
837
|
// Inspect the argument editor.
|
|
752
|
-
const target = input.connection
|
|
753
|
-
|
|
838
|
+
const target = input.connection?.targetBlock()
|
|
839
|
+
if (!target) {
|
|
840
|
+
throw new Error(`Expected argument editor block on input ${input.name}`)
|
|
841
|
+
}
|
|
842
|
+
this.displayNames_.push(String(target.getFieldValue('TEXT')))
|
|
754
843
|
this.argumentIds_.push(input.name)
|
|
755
844
|
if (target.type === 'argument_editor_boolean') {
|
|
756
845
|
this.procCode_ += '%b'
|
|
@@ -758,7 +847,7 @@ function updateDeclarationProcCode_(this: ProcedureDeclarationBlock) {
|
|
|
758
847
|
this.procCode_ += '%s'
|
|
759
848
|
}
|
|
760
849
|
} else {
|
|
761
|
-
throw new Error(
|
|
850
|
+
throw new Error(`Unexpected input type on a procedure mutator root: ${String(input.type)}`)
|
|
762
851
|
}
|
|
763
852
|
}
|
|
764
853
|
}
|
|
@@ -773,8 +862,15 @@ function focusLastEditor_(this: ProcedureDeclarationBlock) {
|
|
|
773
862
|
newInput.fieldRow[0].showEditor()
|
|
774
863
|
} else if (newInput.type === Blockly.inputs.inputTypes.VALUE) {
|
|
775
864
|
// Inspect the argument editor.
|
|
776
|
-
const target = newInput.connection
|
|
777
|
-
target
|
|
865
|
+
const target = newInput.connection?.targetBlock()
|
|
866
|
+
if (!target) {
|
|
867
|
+
throw new Error(`Expected argument editor block on input ${newInput.name}`)
|
|
868
|
+
}
|
|
869
|
+
const field = target.getField('TEXT')
|
|
870
|
+
if (!field) {
|
|
871
|
+
throw new Error(`Expected TEXT field on argument editor block ${target.id}`)
|
|
872
|
+
}
|
|
873
|
+
field.showEditor()
|
|
778
874
|
}
|
|
779
875
|
}
|
|
780
876
|
}
|
|
@@ -843,16 +939,15 @@ function removeFieldCallback(this: ProcedureDeclarationBlock, field: Blockly.Fie
|
|
|
843
939
|
return
|
|
844
940
|
}
|
|
845
941
|
let inputNameToRemove = null
|
|
846
|
-
for (
|
|
847
|
-
const input = this.inputList[n]
|
|
942
|
+
for (const input of this.inputList) {
|
|
848
943
|
if (input.connection) {
|
|
849
|
-
const target = input.connection.targetBlock()
|
|
850
|
-
if (field.name && target.getField(field.name) === field) {
|
|
944
|
+
const target = input.connection.targetBlock()
|
|
945
|
+
if (target && field.name && target.getField(field.name) === field) {
|
|
851
946
|
inputNameToRemove = input.name
|
|
852
947
|
}
|
|
853
948
|
} else {
|
|
854
|
-
for (
|
|
855
|
-
if (
|
|
949
|
+
for (const inputField of input.fieldRow) {
|
|
950
|
+
if (inputField === field) {
|
|
856
951
|
inputNameToRemove = input.name
|
|
857
952
|
}
|
|
858
953
|
}
|
|
@@ -875,9 +970,7 @@ function removeArgumentCallback_(
|
|
|
875
970
|
field: Blockly.Field,
|
|
876
971
|
) {
|
|
877
972
|
const parent = this.getParent()
|
|
878
|
-
|
|
879
|
-
parent.removeFieldCallback(field)
|
|
880
|
-
}
|
|
973
|
+
;(parent as ProcedureDeclarationBlock | null)?.removeFieldCallback(field)
|
|
881
974
|
}
|
|
882
975
|
|
|
883
976
|
/**
|
package/src/blocks/sensing.ts
CHANGED
|
@@ -156,14 +156,14 @@ const OUTPUT_BOOLEAN = function (this: Blockly.Block) {
|
|
|
156
156
|
*/
|
|
157
157
|
const MONITOR_BLOCK = function (this: Blockly.BlockSvg) {
|
|
158
158
|
this.addIcon(new FlyoutCheckboxIcon(this))
|
|
159
|
-
;(this as
|
|
159
|
+
;(this as Blockly.BlockSvg & { checkboxInFlyout?: boolean }).checkboxInFlyout = true
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
/**
|
|
163
163
|
* Mixin to add a context menu for a procedure definition block.
|
|
164
164
|
* It adds the "edit" option and removes the "duplicate" option.
|
|
165
165
|
*/
|
|
166
|
-
const PROCEDURE_DEF_CONTEXTMENU = function (this: Blockly.
|
|
166
|
+
const PROCEDURE_DEF_CONTEXTMENU = function (this: Blockly.BlockSvg) {
|
|
167
167
|
/**
|
|
168
168
|
* Add the "edit" option and removes the "duplicate" option from the context
|
|
169
169
|
* menu.
|
|
@@ -172,6 +172,7 @@ const PROCEDURE_DEF_CONTEXTMENU = function (this: Blockly.Block) {
|
|
|
172
172
|
this.mixin(
|
|
173
173
|
{
|
|
174
174
|
customContextMenu: function (
|
|
175
|
+
this: Blockly.BlockSvg,
|
|
175
176
|
menuOptions: (
|
|
176
177
|
| Blockly.ContextMenuRegistry.ContextMenuOption
|
|
177
178
|
| Blockly.ContextMenuRegistry.LegacyContextMenuOption
|
|
@@ -181,18 +182,20 @@ const PROCEDURE_DEF_CONTEXTMENU = function (this: Blockly.Block) {
|
|
|
181
182
|
menuOptions.push(ScratchProcedures.makeEditOption(this))
|
|
182
183
|
|
|
183
184
|
// Find and remove the duplicate option
|
|
184
|
-
for (let i = 0
|
|
185
|
-
if (
|
|
185
|
+
for (let i = 0; i < menuOptions.length; i++) {
|
|
186
|
+
if (menuOptions[i].text == Blockly.Msg.DUPLICATE_BLOCK) {
|
|
186
187
|
menuOptions.splice(i, 1)
|
|
187
188
|
break
|
|
188
189
|
}
|
|
189
190
|
}
|
|
190
191
|
},
|
|
191
|
-
checkAndDelete: function () {
|
|
192
|
+
checkAndDelete: function (this: Blockly.BlockSvg) {
|
|
192
193
|
const input = this.getInput('custom_block')
|
|
193
194
|
// this is the root block, not the shadow block.
|
|
194
|
-
|
|
195
|
-
|
|
195
|
+
const targetBlock = input?.connection?.targetBlock()
|
|
196
|
+
const targetWithProcCode = targetBlock as (Blockly.Block & { getProcCode?(): string }) | null
|
|
197
|
+
if (targetWithProcCode?.getProcCode) {
|
|
198
|
+
const procCode = targetWithProcCode.getProcCode()
|
|
196
199
|
const didDelete = ScratchProcedures.deleteProcedureDefCallback(procCode, this)
|
|
197
200
|
if (!didDelete) {
|
|
198
201
|
alert(Blockly.Msg.PROCEDURE_USED)
|
|
@@ -227,7 +230,7 @@ const PROCEDURE_CALL_CONTEXTMENU = {
|
|
|
227
230
|
}
|
|
228
231
|
|
|
229
232
|
const SCRATCH_EXTENSION = function (this: Blockly.Block) {
|
|
230
|
-
;(this as
|
|
233
|
+
;(this as Blockly.Block & { isScratchExtension?: boolean }).isScratchExtension = true
|
|
231
234
|
}
|
|
232
235
|
|
|
233
236
|
/**
|
|
@@ -2,13 +2,29 @@
|
|
|
2
2
|
* Copyright 2024 Google LLC
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
|
-
import { ContinuousFlyout } from '@blockly/continuous-toolbox'
|
|
5
|
+
import { ContinuousFlyout, type LabelFlyoutItem } from '@blockly/continuous-toolbox'
|
|
6
6
|
import * as Blockly from 'blockly/core'
|
|
7
7
|
import { CheckboxBubble } from './checkbox_bubble'
|
|
8
8
|
import { StatusIndicatorLabel } from './status_indicator_label'
|
|
9
9
|
import { STATUS_INDICATOR_LABEL_TYPE } from './status_indicator_label_flyout_inflater'
|
|
10
10
|
|
|
11
|
+
interface ReflowElement extends Blockly.BlockSvg {
|
|
12
|
+
checkboxInFlyout?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface CheckboxIcon {
|
|
16
|
+
setChecked: (value: boolean) => void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isCheckboxIcon(icon: Blockly.IIcon | undefined): icon is Blockly.IIcon & CheckboxIcon {
|
|
20
|
+
return !!icon && typeof (icon as { setChecked?: unknown }).setChecked === 'function'
|
|
21
|
+
}
|
|
22
|
+
|
|
11
23
|
export class CheckableContinuousFlyout extends ContinuousFlyout {
|
|
24
|
+
declare protected tabWidth_: number
|
|
25
|
+
declare MARGIN: number
|
|
26
|
+
declare GAP_Y: number
|
|
27
|
+
|
|
12
28
|
/**
|
|
13
29
|
* Creates a new CheckableContinuousFlyout.
|
|
14
30
|
* @param workspaceOptions Configuration options for the flyout workspace.
|
|
@@ -42,7 +58,16 @@ export class CheckableContinuousFlyout extends ContinuousFlyout {
|
|
|
42
58
|
* @param value Value to set the checkbox to.
|
|
43
59
|
*/
|
|
44
60
|
setCheckboxState(blockId: string, value: boolean) {
|
|
45
|
-
this.getWorkspace().getBlockById(blockId)?.getIcon('checkbox')
|
|
61
|
+
const icon = this.getWorkspace().getBlockById(blockId)?.getIcon('checkbox')
|
|
62
|
+
if (!icon) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
if (!isCheckboxIcon(icon)) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`[CheckableContinuousFlyout.setCheckboxState] Expected checkbox icon with setChecked for block ${blockId}`,
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
icon.setChecked(value)
|
|
46
71
|
}
|
|
47
72
|
|
|
48
73
|
getFlyoutScale() {
|
|
@@ -61,14 +86,18 @@ export class CheckableContinuousFlyout extends ContinuousFlyout {
|
|
|
61
86
|
// contents, and adjusts blocks in RTL mode accordingly. In Scratch, the
|
|
62
87
|
// flyout width is fixed (and blocks may exceed it), so re-adjust blocks
|
|
63
88
|
// accordingly based on the actual fixed width.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
89
|
+
const flyoutItems = this.getContents()
|
|
90
|
+
for (const item of flyoutItems) {
|
|
91
|
+
const element = item.getElement()
|
|
92
|
+
if (!(element instanceof Blockly.BlockSvg)) {
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
const oldX = element.getBoundingRectangle().left
|
|
96
|
+
let newX = this.getWidth() / this.workspace_.scale - element.getBoundingRectangle().getWidth() - this.MARGIN
|
|
97
|
+
if ('checkboxInFlyout' in element && (element as ReflowElement).checkboxInFlyout) {
|
|
69
98
|
newX -= CheckboxBubble.CHECKBOX_SIZE + CheckboxBubble.CHECKBOX_MARGIN
|
|
70
99
|
}
|
|
71
|
-
|
|
100
|
+
element.moveBy(newX - oldX, 0)
|
|
72
101
|
}
|
|
73
102
|
}
|
|
74
103
|
}
|
|
@@ -78,8 +107,11 @@ export class CheckableContinuousFlyout extends ContinuousFlyout {
|
|
|
78
107
|
* @param item The toolbox item to check.
|
|
79
108
|
* @returns True if the item represents a label in the flyout.
|
|
80
109
|
*/
|
|
81
|
-
protected toolboxItemIsLabel(item: Blockly.FlyoutItem) {
|
|
82
|
-
|
|
110
|
+
protected toolboxItemIsLabel(item: Blockly.FlyoutItem): item is LabelFlyoutItem {
|
|
111
|
+
if (item.getType() === STATUS_INDICATOR_LABEL_TYPE) {
|
|
112
|
+
return true
|
|
113
|
+
}
|
|
114
|
+
return super.toolboxItemIsLabel(item)
|
|
83
115
|
}
|
|
84
116
|
|
|
85
117
|
/**
|
|
@@ -87,8 +119,9 @@ export class CheckableContinuousFlyout extends ContinuousFlyout {
|
|
|
87
119
|
*/
|
|
88
120
|
refreshStatusButtons() {
|
|
89
121
|
for (const item of this.contents) {
|
|
90
|
-
|
|
91
|
-
|
|
122
|
+
const element = item.getElement()
|
|
123
|
+
if (element instanceof StatusIndicatorLabel) {
|
|
124
|
+
element.refreshStatus()
|
|
92
125
|
}
|
|
93
126
|
}
|
|
94
127
|
}
|
package/src/checkbox_bubble.ts
CHANGED
|
@@ -155,10 +155,10 @@ export class CheckboxBubble implements Blockly.IBubble, Blockly.IRenderedElement
|
|
|
155
155
|
* Returns whether or not the specified block has its checkbox checked.
|
|
156
156
|
*
|
|
157
157
|
* This method is patched by scratch-gui to query the VM state.
|
|
158
|
-
* @param
|
|
158
|
+
* @param _blockId The ID of the block in question.
|
|
159
159
|
* @returns True if the block's checkbox should be checked.
|
|
160
160
|
*/
|
|
161
|
-
isChecked(
|
|
161
|
+
isChecked(_blockId: string): boolean {
|
|
162
162
|
return false
|
|
163
163
|
}
|
|
164
164
|
|
|
@@ -252,17 +252,17 @@ export class CheckboxBubble implements Blockly.IBubble, Blockly.IRenderedElement
|
|
|
252
252
|
// to its block and is not draggable by the user.
|
|
253
253
|
showContextMenu() {}
|
|
254
254
|
|
|
255
|
-
setDragging(
|
|
255
|
+
setDragging(_dragging: boolean) {}
|
|
256
256
|
|
|
257
|
-
startDrag(
|
|
257
|
+
startDrag(_event: PointerEvent) {}
|
|
258
258
|
|
|
259
|
-
drag(
|
|
259
|
+
drag(_newLocation: Blockly.utils.Coordinate, _event: PointerEvent) {}
|
|
260
260
|
|
|
261
|
-
moveDuringDrag(
|
|
261
|
+
moveDuringDrag(_newLocation: Blockly.utils.Coordinate) {}
|
|
262
262
|
|
|
263
263
|
endDrag() {}
|
|
264
264
|
|
|
265
265
|
revertDrag() {}
|
|
266
266
|
|
|
267
|
-
setDeleteStyle(
|
|
267
|
+
setDeleteStyle(_enable: boolean) {}
|
|
268
268
|
}
|
package/src/colours.ts
CHANGED
|
@@ -62,13 +62,15 @@ const Colours = {
|
|
|
62
62
|
* @param prefix A prefix to prepend to the CSS variables.
|
|
63
63
|
* @returns A string containing CSS variable definitions for the colours.
|
|
64
64
|
*/
|
|
65
|
-
function varify(coloursObj:
|
|
65
|
+
function varify(coloursObj: Record<string, unknown>, prefix = '--colour'): string {
|
|
66
66
|
return Object.entries(coloursObj)
|
|
67
67
|
.map(([key, colour]) => {
|
|
68
68
|
if (typeof colour === 'string') {
|
|
69
69
|
return `${prefix}-${key}: ${colour};`
|
|
70
|
+
} else if (typeof colour === 'object' && colour !== null) {
|
|
71
|
+
return varify(colour as Record<string, unknown>, `${prefix}-${key}`)
|
|
70
72
|
} else {
|
|
71
|
-
return
|
|
73
|
+
return ''
|
|
72
74
|
}
|
|
73
75
|
})
|
|
74
76
|
.join('\n')
|