@scratch/scratch-vm 13.6.10 → 13.6.12
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/dist/node/scratch-vm.js +1 -1
- package/dist/web/scratch-vm.js +1 -1
- package/package.json +8 -8
- package/src/engine/blocks.js +9 -2
- package/src/serialization/sb3.js +146 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scratch/scratch-vm",
|
|
3
|
-
"version": "13.6.
|
|
3
|
+
"version": "13.6.12",
|
|
4
4
|
"description": "Virtual Machine for Scratch 3.0",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"homepage": "https://github.com/scratchfoundation/scratch-vm#readme",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"allow-incomplete-coverage": true
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@scratch/scratch-render": "13.6.
|
|
64
|
-
"@scratch/scratch-svg-renderer": "13.6.
|
|
63
|
+
"@scratch/scratch-render": "13.6.12",
|
|
64
|
+
"@scratch/scratch-svg-renderer": "13.6.12",
|
|
65
65
|
"@vernier/godirect": "1.8.3",
|
|
66
66
|
"arraybuffer-loader": "1.0.8",
|
|
67
67
|
"atob": "2.1.2",
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"copy-webpack-plugin": "6.4.1",
|
|
92
92
|
"docdash": "1.2.0",
|
|
93
93
|
"eslint": "9.39.4",
|
|
94
|
-
"eslint-config-scratch": "14.1.
|
|
94
|
+
"eslint-config-scratch": "14.1.10",
|
|
95
95
|
"expose-loader": "1.0.3",
|
|
96
96
|
"file-loader": "6.2.0",
|
|
97
97
|
"format-message-cli": "6.2.4",
|
|
@@ -99,18 +99,18 @@
|
|
|
99
99
|
"in-publish": "2.0.1",
|
|
100
100
|
"js-md5": "0.7.3",
|
|
101
101
|
"pngjs": "3.4.0",
|
|
102
|
-
"scratch-blocks": "2.1.
|
|
103
|
-
"scratch-l10n": "6.1.
|
|
102
|
+
"scratch-blocks": "2.1.17",
|
|
103
|
+
"scratch-l10n": "6.1.70",
|
|
104
104
|
"scratch-render-fonts": "1.0.252",
|
|
105
105
|
"scratch-semantic-release-config": "4.0.1",
|
|
106
106
|
"scratch-webpack-configuration": "3.1.2",
|
|
107
107
|
"script-loader": "0.7.2",
|
|
108
108
|
"semantic-release": "25.0.3",
|
|
109
109
|
"stats.js": "0.17.0",
|
|
110
|
-
"tap": "21.
|
|
110
|
+
"tap": "21.7.0",
|
|
111
111
|
"tiny-worker": "2.3.0",
|
|
112
112
|
"typedoc": "0.28.18",
|
|
113
|
-
"webpack": "5.106.
|
|
113
|
+
"webpack": "5.106.2",
|
|
114
114
|
"webpack-cli": "4.10.0",
|
|
115
115
|
"webpack-dev-server": "5.2.3"
|
|
116
116
|
}
|
package/src/engine/blocks.js
CHANGED
|
@@ -775,8 +775,15 @@ class Blocks {
|
|
|
775
775
|
// this input, or null out the input's block.
|
|
776
776
|
const shadow = oldParent.inputs[e.oldInput].shadow;
|
|
777
777
|
if (shadow && e.id !== shadow) {
|
|
778
|
-
|
|
779
|
-
|
|
778
|
+
if (this._blocks[shadow]) {
|
|
779
|
+
oldParent.inputs[e.oldInput].block = shadow;
|
|
780
|
+
this._blocks[shadow].parent = oldParent.id;
|
|
781
|
+
} else {
|
|
782
|
+
// Shadow block is referenced but missing — clear
|
|
783
|
+
// the stale reference rather than crashing.
|
|
784
|
+
oldParent.inputs[e.oldInput].block = null;
|
|
785
|
+
oldParent.inputs[e.oldInput].shadow = null;
|
|
786
|
+
}
|
|
780
787
|
this._blocks[e.id].parent = null;
|
|
781
788
|
} else {
|
|
782
789
|
oldParent.inputs[e.oldInput].block = null;
|
package/src/serialization/sb3.js
CHANGED
|
@@ -95,6 +95,85 @@ const primitiveOpcodeInfoMap = {
|
|
|
95
95
|
data_listcontents: [LIST_PRIMITIVE, 'LIST']
|
|
96
96
|
};
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Build the fields object for a replacement shadow block. Simple primitives
|
|
100
|
+
* (text, numbers, colours) just need {name, value}. Variable, list, and
|
|
101
|
+
* broadcast shadows also need {id, variableType} for the dropdown to work.
|
|
102
|
+
* @param {string} shadowOpcode The shadow block's opcode.
|
|
103
|
+
* @param {?object} template A peer shadow block to copy field values from, or null.
|
|
104
|
+
* @returns {?object} A fields object for the new shadow, or null if the opcode is unknown.
|
|
105
|
+
*/
|
|
106
|
+
const buildShadowFields = function (shadowOpcode, template) {
|
|
107
|
+
const info = primitiveOpcodeInfoMap[shadowOpcode];
|
|
108
|
+
if (!info) return null;
|
|
109
|
+
const fieldName = info[1];
|
|
110
|
+
const templateField = template && template.fields && template.fields[fieldName];
|
|
111
|
+
const value = templateField ? templateField.value : '';
|
|
112
|
+
|
|
113
|
+
switch (shadowOpcode) {
|
|
114
|
+
case 'event_broadcast_menu':
|
|
115
|
+
return {
|
|
116
|
+
[fieldName]: {
|
|
117
|
+
name: fieldName,
|
|
118
|
+
value: value,
|
|
119
|
+
id: (templateField && templateField.id) || '',
|
|
120
|
+
variableType: Variable.BROADCAST_MESSAGE_TYPE
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
case 'data_variable':
|
|
124
|
+
return {
|
|
125
|
+
[fieldName]: {
|
|
126
|
+
name: fieldName,
|
|
127
|
+
value: value,
|
|
128
|
+
id: (templateField && templateField.id) || '',
|
|
129
|
+
variableType: Variable.SCALAR_TYPE
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
case 'data_listcontents':
|
|
133
|
+
return {
|
|
134
|
+
[fieldName]: {
|
|
135
|
+
name: fieldName,
|
|
136
|
+
value: value,
|
|
137
|
+
id: (templateField && templateField.id) || '',
|
|
138
|
+
variableType: Variable.LIST_TYPE
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
default:
|
|
142
|
+
// Simple primitives: text, math_number, math_angle, colour_picker, etc.
|
|
143
|
+
return {
|
|
144
|
+
[fieldName]: {
|
|
145
|
+
name: fieldName,
|
|
146
|
+
value: value
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Find a working shadow block for the given (opcode, inputName) pair by
|
|
154
|
+
* looking at other blocks of the same opcode in the project. Returns the
|
|
155
|
+
* matching shadow block from the blocks map for use as a template, or
|
|
156
|
+
* null if no peer has a working shadow for that input.
|
|
157
|
+
* @param {object} blocks The full blocks map for the target.
|
|
158
|
+
* @param {string} opcode The parent block's opcode.
|
|
159
|
+
* @param {string} inputName The name of the input.
|
|
160
|
+
* @returns {?object} A template shadow block from the blocks map, or null.
|
|
161
|
+
*/
|
|
162
|
+
const findPeerShadow = function (blocks, opcode, inputName) {
|
|
163
|
+
for (const peerId in blocks) {
|
|
164
|
+
if (!hasOwnProperty.call(blocks, peerId)) continue;
|
|
165
|
+
const peer = blocks[peerId];
|
|
166
|
+
if (Array.isArray(peer) || peer.opcode !== opcode) continue;
|
|
167
|
+
const peerInput = peer.inputs[inputName];
|
|
168
|
+
if (!peerInput || !peerInput.shadow) continue;
|
|
169
|
+
const shadowBlock = blocks[peerInput.shadow];
|
|
170
|
+
if (!shadowBlock || Array.isArray(shadowBlock)) continue;
|
|
171
|
+
if (!hasOwnProperty.call(primitiveOpcodeInfoMap, shadowBlock.opcode)) continue;
|
|
172
|
+
return shadowBlock;
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
};
|
|
176
|
+
|
|
98
177
|
/**
|
|
99
178
|
* Serializes primitives described above into a more compact format
|
|
100
179
|
* @param {object} block the block to serialize
|
|
@@ -885,6 +964,73 @@ const deserializeBlocks = function (blocks) {
|
|
|
885
964
|
block.inputs = {};
|
|
886
965
|
}
|
|
887
966
|
|
|
967
|
+
// Third pass: repair missing or broken shadow blocks. Shadows can be
|
|
968
|
+
// missing in two ways:
|
|
969
|
+
// 1. shadow is a stale ID pointing to a nonexistent block
|
|
970
|
+
// 2. shadow is null when it shouldn't be (lost before save)
|
|
971
|
+
// Both are leftovers from a serialization bug where shadow blocks were
|
|
972
|
+
// dropped during save. Recreate the shadow by finding a peer block of
|
|
973
|
+
// the same opcode with an intact shadow on the same input (peer lookup).
|
|
974
|
+
// For broken references (case 1), fall back to a text shadow if no peer
|
|
975
|
+
// is available. For missing shadows (case 2), only create a shadow if a
|
|
976
|
+
// peer confirms one should exist — otherwise the input probably doesn't
|
|
977
|
+
// use shadows (e.g. statement inputs like SUBSTACK).
|
|
978
|
+
// Cache peer lookups per (opcode, inputName) to avoid repeated O(n) scans.
|
|
979
|
+
const peerShadowCache = Object.create(null);
|
|
980
|
+
for (const blockId in blocks) {
|
|
981
|
+
if (!Object.prototype.hasOwnProperty.call(blocks, blockId)) continue;
|
|
982
|
+
const block = blocks[blockId];
|
|
983
|
+
if (Array.isArray(block)) continue;
|
|
984
|
+
for (const inputName in block.inputs) {
|
|
985
|
+
if (!Object.prototype.hasOwnProperty.call(block.inputs, inputName)) continue;
|
|
986
|
+
const input = block.inputs[inputName];
|
|
987
|
+
|
|
988
|
+
const shadowIsBroken = input.shadow &&
|
|
989
|
+
!Object.prototype.hasOwnProperty.call(blocks, input.shadow);
|
|
990
|
+
const shadowIsMissing = !input.shadow && input.block &&
|
|
991
|
+
Object.prototype.hasOwnProperty.call(blocks, input.block) &&
|
|
992
|
+
!blocks[input.block].shadow;
|
|
993
|
+
|
|
994
|
+
if (!shadowIsBroken && !shadowIsMissing) continue;
|
|
995
|
+
|
|
996
|
+
// Try to find a peer block with a working shadow for this input.
|
|
997
|
+
const cacheKey = `${block.opcode}/${inputName}`;
|
|
998
|
+
if (!Object.prototype.hasOwnProperty.call(peerShadowCache, cacheKey)) {
|
|
999
|
+
peerShadowCache[cacheKey] = findPeerShadow(blocks, block.opcode, inputName);
|
|
1000
|
+
}
|
|
1001
|
+
const template = peerShadowCache[cacheKey];
|
|
1002
|
+
if (!template && !shadowIsBroken) {
|
|
1003
|
+
// No peer has a shadow either — this input probably doesn't
|
|
1004
|
+
// use one (e.g. custom procedure inputs). Leave it alone.
|
|
1005
|
+
continue;
|
|
1006
|
+
}
|
|
1007
|
+
const shadowOpcode = template ? template.opcode : 'text';
|
|
1008
|
+
const fields = buildShadowFields(shadowOpcode, template);
|
|
1009
|
+
if (!fields) {
|
|
1010
|
+
// Unknown shadow type — clear any stale reference
|
|
1011
|
+
// so it doesn't cause crashes.
|
|
1012
|
+
if (shadowIsBroken) input.shadow = null;
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
const newShadowId = uid();
|
|
1016
|
+
blocks[newShadowId] = {
|
|
1017
|
+
id: newShadowId,
|
|
1018
|
+
opcode: shadowOpcode,
|
|
1019
|
+
next: null,
|
|
1020
|
+
parent: blockId,
|
|
1021
|
+
shadow: true,
|
|
1022
|
+
topLevel: false,
|
|
1023
|
+
inputs: Object.create(null),
|
|
1024
|
+
fields: fields
|
|
1025
|
+
};
|
|
1026
|
+
input.shadow = newShadowId;
|
|
1027
|
+
// If no block is connected, also set block to the shadow
|
|
1028
|
+
if (!input.block || !Object.prototype.hasOwnProperty.call(blocks, input.block)) {
|
|
1029
|
+
input.block = newShadowId;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
888
1034
|
return blocks;
|
|
889
1035
|
};
|
|
890
1036
|
|