@scratch/scratch-vm 11.1.0-spork.9 → 11.1.0
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 +12 -12
- package/src/engine/blocks.js +133 -373
- package/src/engine/comment.js +1 -1
- package/src/engine/runtime.js +188 -405
package/src/engine/runtime.js
CHANGED
|
@@ -385,8 +385,7 @@ class Runtime extends EventEmitter {
|
|
|
385
385
|
* being added.
|
|
386
386
|
* @type {function}
|
|
387
387
|
*/
|
|
388
|
-
this.addCloudVariable =
|
|
389
|
-
this._initializeAddCloudVariable(newCloudDataManager);
|
|
388
|
+
this.addCloudVariable = this._initializeAddCloudVariable(newCloudDataManager);
|
|
390
389
|
|
|
391
390
|
/**
|
|
392
391
|
* A function which updates the runtime's cloud variable limit
|
|
@@ -394,8 +393,7 @@ class Runtime extends EventEmitter {
|
|
|
394
393
|
* if the last of the cloud variables is being removed.
|
|
395
394
|
* @type {function}
|
|
396
395
|
*/
|
|
397
|
-
this.removeCloudVariable =
|
|
398
|
-
this._initializeRemoveCloudVariable(newCloudDataManager);
|
|
396
|
+
this.removeCloudVariable = this._initializeRemoveCloudVariable(newCloudDataManager);
|
|
399
397
|
|
|
400
398
|
/**
|
|
401
399
|
* A string representing the origin of the current project from outside of the
|
|
@@ -739,24 +737,24 @@ class Runtime extends EventEmitter {
|
|
|
739
737
|
// Helper function for initializing the addCloudVariable function
|
|
740
738
|
_initializeAddCloudVariable (newCloudDataManager) {
|
|
741
739
|
// The addCloudVariable function
|
|
742
|
-
return () => {
|
|
740
|
+
return (() => {
|
|
743
741
|
const hadCloudVarsBefore = this.hasCloudData();
|
|
744
742
|
newCloudDataManager.addCloudVariable();
|
|
745
743
|
if (!hadCloudVarsBefore && this.hasCloudData()) {
|
|
746
744
|
this.emit(Runtime.HAS_CLOUD_DATA_UPDATE, true);
|
|
747
745
|
}
|
|
748
|
-
};
|
|
746
|
+
});
|
|
749
747
|
}
|
|
750
748
|
|
|
751
749
|
// Helper function for initializing the removeCloudVariable function
|
|
752
750
|
_initializeRemoveCloudVariable (newCloudDataManager) {
|
|
753
|
-
return () => {
|
|
751
|
+
return (() => {
|
|
754
752
|
const hadCloudVarsBefore = this.hasCloudData();
|
|
755
753
|
newCloudDataManager.removeCloudVariable();
|
|
756
754
|
if (hadCloudVarsBefore && !this.hasCloudData()) {
|
|
757
755
|
this.emit(Runtime.HAS_CLOUD_DATA_UPDATE, false);
|
|
758
756
|
}
|
|
759
|
-
};
|
|
757
|
+
});
|
|
760
758
|
}
|
|
761
759
|
|
|
762
760
|
/**
|
|
@@ -766,26 +764,14 @@ class Runtime extends EventEmitter {
|
|
|
766
764
|
*/
|
|
767
765
|
_registerBlockPackages () {
|
|
768
766
|
for (const packageName in defaultBlockPackages) {
|
|
769
|
-
if (
|
|
770
|
-
Object.prototype.hasOwnProperty.call(
|
|
771
|
-
defaultBlockPackages,
|
|
772
|
-
packageName
|
|
773
|
-
)
|
|
774
|
-
) {
|
|
767
|
+
if (Object.prototype.hasOwnProperty.call(defaultBlockPackages, packageName)) {
|
|
775
768
|
// @todo pass a different runtime depending on package privilege?
|
|
776
|
-
const packageObject = new defaultBlockPackages[packageName](
|
|
777
|
-
this
|
|
778
|
-
);
|
|
769
|
+
const packageObject = new (defaultBlockPackages[packageName])(this);
|
|
779
770
|
// Collect primitives from package.
|
|
780
771
|
if (packageObject.getPrimitives) {
|
|
781
772
|
const packagePrimitives = packageObject.getPrimitives();
|
|
782
773
|
for (const op in packagePrimitives) {
|
|
783
|
-
if (
|
|
784
|
-
Object.prototype.hasOwnProperty.call(
|
|
785
|
-
packagePrimitives,
|
|
786
|
-
op
|
|
787
|
-
)
|
|
788
|
-
) {
|
|
774
|
+
if (Object.prototype.hasOwnProperty.call(packagePrimitives, op)) {
|
|
789
775
|
this._primitives[op] =
|
|
790
776
|
packagePrimitives[op].bind(packageObject);
|
|
791
777
|
}
|
|
@@ -795,23 +781,14 @@ class Runtime extends EventEmitter {
|
|
|
795
781
|
if (packageObject.getHats) {
|
|
796
782
|
const packageHats = packageObject.getHats();
|
|
797
783
|
for (const hatName in packageHats) {
|
|
798
|
-
if (
|
|
799
|
-
Object.prototype.hasOwnProperty.call(
|
|
800
|
-
packageHats,
|
|
801
|
-
hatName
|
|
802
|
-
)
|
|
803
|
-
) {
|
|
784
|
+
if (Object.prototype.hasOwnProperty.call(packageHats, hatName)) {
|
|
804
785
|
this._hats[hatName] = packageHats[hatName];
|
|
805
786
|
}
|
|
806
787
|
}
|
|
807
788
|
}
|
|
808
789
|
// Collect monitored from package.
|
|
809
790
|
if (packageObject.getMonitored) {
|
|
810
|
-
this.monitorBlockInfo = Object.assign(
|
|
811
|
-
{},
|
|
812
|
-
this.monitorBlockInfo,
|
|
813
|
-
packageObject.getMonitored()
|
|
814
|
-
);
|
|
791
|
+
this.monitorBlockInfo = Object.assign({}, this.monitorBlockInfo, packageObject.getMonitored());
|
|
815
792
|
}
|
|
816
793
|
}
|
|
817
794
|
}
|
|
@@ -841,9 +818,7 @@ class Runtime extends EventEmitter {
|
|
|
841
818
|
const context = {};
|
|
842
819
|
target = target || this.getEditingTarget() || this.getTargetForStage();
|
|
843
820
|
if (target) {
|
|
844
|
-
context.targetType = target.isStage ?
|
|
845
|
-
TargetType.STAGE :
|
|
846
|
-
TargetType.SPRITE;
|
|
821
|
+
context.targetType = (target.isStage ? TargetType.STAGE : TargetType.SPRITE);
|
|
847
822
|
}
|
|
848
823
|
}
|
|
849
824
|
|
|
@@ -876,14 +851,8 @@ class Runtime extends EventEmitter {
|
|
|
876
851
|
this._fillExtensionCategory(categoryInfo, extensionInfo);
|
|
877
852
|
|
|
878
853
|
for (const fieldTypeName in categoryInfo.customFieldTypes) {
|
|
879
|
-
if (
|
|
880
|
-
|
|
881
|
-
extensionInfo.customFieldTypes,
|
|
882
|
-
fieldTypeName
|
|
883
|
-
)
|
|
884
|
-
) {
|
|
885
|
-
const fieldTypeInfo =
|
|
886
|
-
categoryInfo.customFieldTypes[fieldTypeName];
|
|
854
|
+
if (Object.prototype.hasOwnProperty.call(extensionInfo.customFieldTypes, fieldTypeName)) {
|
|
855
|
+
const fieldTypeInfo = categoryInfo.customFieldTypes[fieldTypeName];
|
|
887
856
|
|
|
888
857
|
// Emit events for custom field types from extension
|
|
889
858
|
this.emit(Runtime.EXTENSION_FIELD_ADDED, {
|
|
@@ -902,9 +871,7 @@ class Runtime extends EventEmitter {
|
|
|
902
871
|
* @private
|
|
903
872
|
*/
|
|
904
873
|
_refreshExtensionPrimitives (extensionInfo) {
|
|
905
|
-
const categoryInfo = this._blockInfo.find(
|
|
906
|
-
info => info.id === extensionInfo.id
|
|
907
|
-
);
|
|
874
|
+
const categoryInfo = this._blockInfo.find(info => info.id === extensionInfo.id);
|
|
908
875
|
if (categoryInfo) {
|
|
909
876
|
categoryInfo.name = maybeFormatMessage(extensionInfo.name);
|
|
910
877
|
this._fillExtensionCategory(categoryInfo, extensionInfo);
|
|
@@ -927,29 +894,15 @@ class Runtime extends EventEmitter {
|
|
|
927
894
|
categoryInfo.menuInfo = {};
|
|
928
895
|
|
|
929
896
|
for (const menuName in extensionInfo.menus) {
|
|
930
|
-
if (
|
|
931
|
-
Object.prototype.hasOwnProperty.call(
|
|
932
|
-
extensionInfo.menus,
|
|
933
|
-
menuName
|
|
934
|
-
)
|
|
935
|
-
) {
|
|
897
|
+
if (Object.prototype.hasOwnProperty.call(extensionInfo.menus, menuName)) {
|
|
936
898
|
const menuInfo = extensionInfo.menus[menuName];
|
|
937
|
-
const convertedMenu = this._buildMenuForScratchBlocks(
|
|
938
|
-
menuName,
|
|
939
|
-
menuInfo,
|
|
940
|
-
categoryInfo
|
|
941
|
-
);
|
|
899
|
+
const convertedMenu = this._buildMenuForScratchBlocks(menuName, menuInfo, categoryInfo);
|
|
942
900
|
categoryInfo.menus.push(convertedMenu);
|
|
943
901
|
categoryInfo.menuInfo[menuName] = menuInfo;
|
|
944
902
|
}
|
|
945
903
|
}
|
|
946
904
|
for (const fieldTypeName in extensionInfo.customFieldTypes) {
|
|
947
|
-
if (
|
|
948
|
-
Object.prototype.hasOwnProperty.call(
|
|
949
|
-
extensionInfo.customFieldTypes,
|
|
950
|
-
fieldTypeName
|
|
951
|
-
)
|
|
952
|
-
) {
|
|
905
|
+
if (Object.prototype.hasOwnProperty.call(extensionInfo.customFieldTypes, fieldTypeName)) {
|
|
953
906
|
const fieldType = extensionInfo.customFieldTypes[fieldTypeName];
|
|
954
907
|
const fieldTypeInfo = this._buildCustomFieldInfo(
|
|
955
908
|
fieldTypeName,
|
|
@@ -964,32 +917,22 @@ class Runtime extends EventEmitter {
|
|
|
964
917
|
|
|
965
918
|
for (const blockInfo of extensionInfo.blocks) {
|
|
966
919
|
try {
|
|
967
|
-
const convertedBlock = this._convertForScratchBlocks(
|
|
968
|
-
blockInfo,
|
|
969
|
-
categoryInfo
|
|
970
|
-
);
|
|
920
|
+
const convertedBlock = this._convertForScratchBlocks(blockInfo, categoryInfo);
|
|
971
921
|
categoryInfo.blocks.push(convertedBlock);
|
|
972
922
|
if (convertedBlock.json) {
|
|
973
923
|
const opcode = convertedBlock.json.type;
|
|
974
924
|
if (blockInfo.blockType !== BlockType.EVENT) {
|
|
975
925
|
this._primitives[opcode] = convertedBlock.info.func;
|
|
976
926
|
}
|
|
977
|
-
if (
|
|
978
|
-
blockInfo.blockType === BlockType.EVENT ||
|
|
979
|
-
blockInfo.blockType === BlockType.HAT
|
|
980
|
-
) {
|
|
927
|
+
if (blockInfo.blockType === BlockType.EVENT || blockInfo.blockType === BlockType.HAT) {
|
|
981
928
|
this._hats[opcode] = {
|
|
982
929
|
edgeActivated: blockInfo.isEdgeActivated,
|
|
983
|
-
restartExistingThreads:
|
|
984
|
-
blockInfo.shouldRestartExistingThreads
|
|
930
|
+
restartExistingThreads: blockInfo.shouldRestartExistingThreads
|
|
985
931
|
};
|
|
986
932
|
}
|
|
987
933
|
}
|
|
988
934
|
} catch (e) {
|
|
989
|
-
log.error('Error parsing block: ', {
|
|
990
|
-
block: blockInfo,
|
|
991
|
-
error: e
|
|
992
|
-
});
|
|
935
|
+
log.error('Error parsing block: ', {block: blockInfo, error: e});
|
|
993
936
|
}
|
|
994
937
|
}
|
|
995
938
|
}
|
|
@@ -1005,25 +948,14 @@ class Runtime extends EventEmitter {
|
|
|
1005
948
|
if (typeof menuItems !== 'function') {
|
|
1006
949
|
const extensionMessageContext = this.makeMessageContextForTarget();
|
|
1007
950
|
return menuItems.map(item => {
|
|
1008
|
-
const formattedItem = maybeFormatMessage(
|
|
1009
|
-
item,
|
|
1010
|
-
extensionMessageContext
|
|
1011
|
-
);
|
|
951
|
+
const formattedItem = maybeFormatMessage(item, extensionMessageContext);
|
|
1012
952
|
switch (typeof formattedItem) {
|
|
1013
953
|
case 'string':
|
|
1014
954
|
return [formattedItem, formattedItem];
|
|
1015
955
|
case 'object':
|
|
1016
|
-
return [
|
|
1017
|
-
maybeFormatMessage(
|
|
1018
|
-
item.text,
|
|
1019
|
-
extensionMessageContext
|
|
1020
|
-
),
|
|
1021
|
-
item.value
|
|
1022
|
-
];
|
|
956
|
+
return [maybeFormatMessage(item.text, extensionMessageContext), item.value];
|
|
1023
957
|
default:
|
|
1024
|
-
throw new Error(
|
|
1025
|
-
`Can't interpret menu item: ${JSON.stringify(item)}`
|
|
1026
|
-
);
|
|
958
|
+
throw new Error(`Can't interpret menu item: ${JSON.stringify(item)}`);
|
|
1027
959
|
}
|
|
1028
960
|
});
|
|
1029
961
|
}
|
|
@@ -1049,10 +981,11 @@ class Runtime extends EventEmitter {
|
|
|
1049
981
|
type: menuId,
|
|
1050
982
|
inputsInline: true,
|
|
1051
983
|
output: 'String',
|
|
1052
|
-
|
|
984
|
+
colour: categoryInfo.color1,
|
|
985
|
+
colourSecondary: categoryInfo.color2,
|
|
986
|
+
colourTertiary: categoryInfo.color3,
|
|
1053
987
|
outputShape: menuInfo.acceptReporters ?
|
|
1054
|
-
ScratchBlocksConstants.OUTPUT_SHAPE_ROUND :
|
|
1055
|
-
ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE,
|
|
988
|
+
ScratchBlocksConstants.OUTPUT_SHAPE_ROUND : ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE,
|
|
1056
989
|
args0: [
|
|
1057
990
|
{
|
|
1058
991
|
type: 'field_dropdown',
|
|
@@ -1094,19 +1027,16 @@ class Runtime extends EventEmitter {
|
|
|
1094
1027
|
* @param {object} categoryInfo - The category the field belongs to (Used to set its colors)
|
|
1095
1028
|
* @returns {object} - Object to be inserted into scratch-blocks
|
|
1096
1029
|
*/
|
|
1097
|
-
_buildCustomFieldTypeForScratchBlocks (
|
|
1098
|
-
fieldName,
|
|
1099
|
-
output,
|
|
1100
|
-
outputShape,
|
|
1101
|
-
categoryInfo
|
|
1102
|
-
) {
|
|
1030
|
+
_buildCustomFieldTypeForScratchBlocks (fieldName, output, outputShape, categoryInfo) {
|
|
1103
1031
|
return {
|
|
1104
1032
|
json: {
|
|
1105
1033
|
type: fieldName,
|
|
1106
1034
|
message0: '%1',
|
|
1107
1035
|
inputsInline: true,
|
|
1108
1036
|
output: output,
|
|
1109
|
-
|
|
1037
|
+
colour: categoryInfo.color1,
|
|
1038
|
+
colourSecondary: categoryInfo.color2,
|
|
1039
|
+
colourTertiary: categoryInfo.color3,
|
|
1110
1040
|
outputShape: outputShape,
|
|
1111
1041
|
args0: [
|
|
1112
1042
|
{
|
|
@@ -1151,8 +1081,9 @@ class Runtime extends EventEmitter {
|
|
|
1151
1081
|
type: extendedOpcode,
|
|
1152
1082
|
inputsInline: true,
|
|
1153
1083
|
category: categoryInfo.name,
|
|
1154
|
-
|
|
1155
|
-
|
|
1084
|
+
colour: categoryInfo.color1,
|
|
1085
|
+
colourSecondary: categoryInfo.color2,
|
|
1086
|
+
colourTertiary: categoryInfo.color3
|
|
1156
1087
|
};
|
|
1157
1088
|
const context = {
|
|
1158
1089
|
// TODO: store this somewhere so that we can map args appropriately after translation.
|
|
@@ -1172,7 +1103,7 @@ class Runtime extends EventEmitter {
|
|
|
1172
1103
|
const iconURI = blockInfo.blockIconURI || categoryInfo.blockIconURI;
|
|
1173
1104
|
|
|
1174
1105
|
if (iconURI) {
|
|
1175
|
-
blockJSON.extensions
|
|
1106
|
+
blockJSON.extensions = ['scratch_extension'];
|
|
1176
1107
|
blockJSON.message0 = '%1 %2';
|
|
1177
1108
|
const iconJSON = {
|
|
1178
1109
|
type: 'field_image',
|
|
@@ -1183,13 +1114,15 @@ class Runtime extends EventEmitter {
|
|
|
1183
1114
|
const separatorJSON = {
|
|
1184
1115
|
type: 'field_vertical_separator'
|
|
1185
1116
|
};
|
|
1186
|
-
blockJSON.args0 = [
|
|
1117
|
+
blockJSON.args0 = [
|
|
1118
|
+
iconJSON,
|
|
1119
|
+
separatorJSON
|
|
1120
|
+
];
|
|
1187
1121
|
}
|
|
1188
1122
|
|
|
1189
1123
|
switch (blockInfo.blockType) {
|
|
1190
1124
|
case BlockType.COMMAND:
|
|
1191
|
-
blockJSON.outputShape =
|
|
1192
|
-
ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE;
|
|
1125
|
+
blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE;
|
|
1193
1126
|
blockJSON.previousStatement = null; // null = available connection; undefined = hat
|
|
1194
1127
|
if (!blockInfo.isTerminal) {
|
|
1195
1128
|
blockJSON.nextStatement = null; // null = available connection; undefined = terminal
|
|
@@ -1197,35 +1130,25 @@ class Runtime extends EventEmitter {
|
|
|
1197
1130
|
break;
|
|
1198
1131
|
case BlockType.REPORTER:
|
|
1199
1132
|
blockJSON.output = 'String'; // TODO: distinguish number & string here?
|
|
1200
|
-
blockJSON.outputShape =
|
|
1201
|
-
ScratchBlocksConstants.OUTPUT_SHAPE_ROUND;
|
|
1133
|
+
blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_ROUND;
|
|
1202
1134
|
break;
|
|
1203
1135
|
case BlockType.BOOLEAN:
|
|
1204
1136
|
blockJSON.output = 'Boolean';
|
|
1205
|
-
blockJSON.outputShape =
|
|
1206
|
-
ScratchBlocksConstants.OUTPUT_SHAPE_HEXAGONAL;
|
|
1137
|
+
blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_HEXAGONAL;
|
|
1207
1138
|
break;
|
|
1208
1139
|
case BlockType.HAT:
|
|
1209
1140
|
case BlockType.EVENT:
|
|
1210
|
-
if (
|
|
1211
|
-
!Object.prototype.hasOwnProperty.call(
|
|
1212
|
-
blockInfo,
|
|
1213
|
-
'isEdgeActivated'
|
|
1214
|
-
)
|
|
1215
|
-
) {
|
|
1141
|
+
if (!Object.prototype.hasOwnProperty.call(blockInfo, 'isEdgeActivated')) {
|
|
1216
1142
|
// if absent, this property defaults to true
|
|
1217
1143
|
blockInfo.isEdgeActivated = true;
|
|
1218
1144
|
}
|
|
1219
|
-
blockJSON.outputShape =
|
|
1220
|
-
ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE;
|
|
1145
|
+
blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE;
|
|
1221
1146
|
blockJSON.nextStatement = null; // null = available connection; undefined = terminal
|
|
1222
|
-
blockJSON.extensions.push('shape_hat');
|
|
1223
1147
|
break;
|
|
1224
1148
|
case BlockType.CONDITIONAL:
|
|
1225
1149
|
case BlockType.LOOP:
|
|
1226
1150
|
blockInfo.branchCount = blockInfo.branchCount || 1;
|
|
1227
|
-
blockJSON.outputShape =
|
|
1228
|
-
ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE;
|
|
1151
|
+
blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE;
|
|
1229
1152
|
blockJSON.previousStatement = null; // null = available connection; undefined = hat
|
|
1230
1153
|
if (!blockInfo.isTerminal) {
|
|
1231
1154
|
blockJSON.nextStatement = null; // null = available connection; undefined = terminal
|
|
@@ -1233,33 +1156,19 @@ class Runtime extends EventEmitter {
|
|
|
1233
1156
|
break;
|
|
1234
1157
|
}
|
|
1235
1158
|
|
|
1236
|
-
const blockText = Array.isArray(blockInfo.text) ?
|
|
1237
|
-
blockInfo.text :
|
|
1238
|
-
[blockInfo.text];
|
|
1159
|
+
const blockText = Array.isArray(blockInfo.text) ? blockInfo.text : [blockInfo.text];
|
|
1239
1160
|
let inTextNum = 0; // text for the next block "arm" is blockText[inTextNum]
|
|
1240
1161
|
let inBranchNum = 0; // how many branches have we placed into the JSON so far?
|
|
1241
1162
|
let outLineNum = 0; // used for scratch-blocks `message${outLineNum}` and `args${outLineNum}`
|
|
1242
|
-
const convertPlaceholders = this._convertPlaceholders.bind(
|
|
1243
|
-
this,
|
|
1244
|
-
context
|
|
1245
|
-
);
|
|
1163
|
+
const convertPlaceholders = this._convertPlaceholders.bind(this, context);
|
|
1246
1164
|
const extensionMessageContext = this.makeMessageContextForTarget();
|
|
1247
1165
|
|
|
1248
1166
|
// alternate between a block "arm" with text on it and an open slot for a substack
|
|
1249
|
-
while (
|
|
1250
|
-
inTextNum < blockText.length ||
|
|
1251
|
-
inBranchNum < blockInfo.branchCount
|
|
1252
|
-
) {
|
|
1167
|
+
while (inTextNum < blockText.length || inBranchNum < blockInfo.branchCount) {
|
|
1253
1168
|
if (inTextNum < blockText.length) {
|
|
1254
1169
|
context.outLineNum = outLineNum;
|
|
1255
|
-
const lineText = maybeFormatMessage(
|
|
1256
|
-
|
|
1257
|
-
extensionMessageContext
|
|
1258
|
-
);
|
|
1259
|
-
const convertedText = lineText.replace(
|
|
1260
|
-
/\[(.+?)]/g,
|
|
1261
|
-
convertPlaceholders
|
|
1262
|
-
);
|
|
1170
|
+
const lineText = maybeFormatMessage(blockText[inTextNum], extensionMessageContext);
|
|
1171
|
+
const convertedText = lineText.replace(/\[(.+?)]/g, convertPlaceholders);
|
|
1263
1172
|
if (blockJSON[`message${outLineNum}`]) {
|
|
1264
1173
|
blockJSON[`message${outLineNum}`] += convertedText;
|
|
1265
1174
|
} else {
|
|
@@ -1270,14 +1179,10 @@ class Runtime extends EventEmitter {
|
|
|
1270
1179
|
}
|
|
1271
1180
|
if (inBranchNum < blockInfo.branchCount) {
|
|
1272
1181
|
blockJSON[`message${outLineNum}`] = '%1';
|
|
1273
|
-
blockJSON[`args${outLineNum}`] = [
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
inBranchNum > 0 ? inBranchNum + 1 : ''
|
|
1278
|
-
}`
|
|
1279
|
-
}
|
|
1280
|
-
];
|
|
1182
|
+
blockJSON[`args${outLineNum}`] = [{
|
|
1183
|
+
type: 'input_statement',
|
|
1184
|
+
name: `SUBSTACK${inBranchNum > 0 ? inBranchNum + 1 : ''}`
|
|
1185
|
+
}];
|
|
1281
1186
|
++inBranchNum;
|
|
1282
1187
|
++outLineNum;
|
|
1283
1188
|
}
|
|
@@ -1285,28 +1190,24 @@ class Runtime extends EventEmitter {
|
|
|
1285
1190
|
|
|
1286
1191
|
if (blockInfo.blockType === BlockType.REPORTER) {
|
|
1287
1192
|
if (!blockInfo.disableMonitor && context.inputList.length === 0) {
|
|
1288
|
-
blockJSON.
|
|
1193
|
+
blockJSON.checkboxInFlyout = true;
|
|
1289
1194
|
}
|
|
1290
1195
|
} else if (blockInfo.blockType === BlockType.LOOP) {
|
|
1291
1196
|
// Add icon to the bottom right of a loop block
|
|
1292
1197
|
blockJSON[`lastDummyAlign${outLineNum}`] = 'RIGHT';
|
|
1293
1198
|
blockJSON[`message${outLineNum}`] = '%1';
|
|
1294
|
-
blockJSON[`args${outLineNum}`] = [
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
}
|
|
1303
|
-
];
|
|
1199
|
+
blockJSON[`args${outLineNum}`] = [{
|
|
1200
|
+
type: 'field_image',
|
|
1201
|
+
src: './static/blocks-media/repeat.svg', // TODO: use a constant or make this configurable?
|
|
1202
|
+
width: 24,
|
|
1203
|
+
height: 24,
|
|
1204
|
+
alt: '*', // TODO remove this since we don't use collapsed blocks in scratch
|
|
1205
|
+
flip_rtl: true
|
|
1206
|
+
}];
|
|
1304
1207
|
++outLineNum;
|
|
1305
1208
|
}
|
|
1306
1209
|
|
|
1307
|
-
const mutation = blockInfo.isDynamic ?
|
|
1308
|
-
`<mutation blockInfo="${xmlEscape(JSON.stringify(blockInfo))}"/>` :
|
|
1309
|
-
'';
|
|
1210
|
+
const mutation = blockInfo.isDynamic ? `<mutation blockInfo="${xmlEscape(JSON.stringify(blockInfo))}"/>` : '';
|
|
1310
1211
|
const inputs = context.inputList.join('');
|
|
1311
1212
|
const blockXML = `<block type="${extendedOpcode}">${mutation}${inputs}</block>`;
|
|
1312
1213
|
|
|
@@ -1341,22 +1242,13 @@ class Runtime extends EventEmitter {
|
|
|
1341
1242
|
*/
|
|
1342
1243
|
_convertButtonForScratchBlocks (buttonInfo) {
|
|
1343
1244
|
// for now we only support these pre-defined callbacks handled in scratch-blocks
|
|
1344
|
-
const supportedCallbackKeys = [
|
|
1345
|
-
'MAKE_A_LIST',
|
|
1346
|
-
'MAKE_A_PROCEDURE',
|
|
1347
|
-
'MAKE_A_VARIABLE'
|
|
1348
|
-
];
|
|
1245
|
+
const supportedCallbackKeys = ['MAKE_A_LIST', 'MAKE_A_PROCEDURE', 'MAKE_A_VARIABLE'];
|
|
1349
1246
|
if (supportedCallbackKeys.indexOf(buttonInfo.func) < 0) {
|
|
1350
|
-
log.error(
|
|
1351
|
-
`Custom button callbacks not supported yet: ${buttonInfo.func}`
|
|
1352
|
-
);
|
|
1247
|
+
log.error(`Custom button callbacks not supported yet: ${buttonInfo.func}`);
|
|
1353
1248
|
}
|
|
1354
1249
|
|
|
1355
1250
|
const extensionMessageContext = this.makeMessageContextForTarget();
|
|
1356
|
-
const buttonText = maybeFormatMessage(
|
|
1357
|
-
buttonInfo.text,
|
|
1358
|
-
extensionMessageContext
|
|
1359
|
-
);
|
|
1251
|
+
const buttonText = maybeFormatMessage(buttonInfo.text, extensionMessageContext);
|
|
1360
1252
|
return {
|
|
1361
1253
|
info: buttonInfo,
|
|
1362
1254
|
xml: `<button text="${buttonText}" callbackKey="${buttonInfo.func}"></button>`
|
|
@@ -1371,9 +1263,7 @@ class Runtime extends EventEmitter {
|
|
|
1371
1263
|
*/
|
|
1372
1264
|
_constructInlineImageJson (argInfo) {
|
|
1373
1265
|
if (!argInfo.dataURI) {
|
|
1374
|
-
log.warn(
|
|
1375
|
-
'Missing data URI in extension block with argument type IMAGE'
|
|
1376
|
-
);
|
|
1266
|
+
log.warn('Missing data URI in extension block with argument type IMAGE');
|
|
1377
1267
|
}
|
|
1378
1268
|
return {
|
|
1379
1269
|
type: 'field_image',
|
|
@@ -1406,13 +1296,8 @@ class Runtime extends EventEmitter {
|
|
|
1406
1296
|
let argTypeInfo = ArgumentTypeMap[argInfo.type] || {};
|
|
1407
1297
|
|
|
1408
1298
|
// Field type not a standard field type, see if extension has registered custom field type
|
|
1409
|
-
if (
|
|
1410
|
-
|
|
1411
|
-
context.categoryInfo.customFieldTypes[argInfo.type]
|
|
1412
|
-
) {
|
|
1413
|
-
argTypeInfo =
|
|
1414
|
-
context.categoryInfo.customFieldTypes[argInfo.type]
|
|
1415
|
-
.argumentTypeInfo;
|
|
1299
|
+
if (!ArgumentTypeMap[argInfo.type] && context.categoryInfo.customFieldTypes[argInfo.type]) {
|
|
1300
|
+
argTypeInfo = context.categoryInfo.customFieldTypes[argInfo.type].argumentTypeInfo;
|
|
1416
1301
|
}
|
|
1417
1302
|
|
|
1418
1303
|
// Start to construct the scratch-blocks style JSON defining how the block should be
|
|
@@ -1433,14 +1318,8 @@ class Runtime extends EventEmitter {
|
|
|
1433
1318
|
};
|
|
1434
1319
|
|
|
1435
1320
|
const defaultValue =
|
|
1436
|
-
typeof argInfo.defaultValue === 'undefined' ?
|
|
1437
|
-
|
|
1438
|
-
xmlEscape(
|
|
1439
|
-
maybeFormatMessage(
|
|
1440
|
-
argInfo.defaultValue,
|
|
1441
|
-
this.makeMessageContextForTarget()
|
|
1442
|
-
).toString()
|
|
1443
|
-
);
|
|
1321
|
+
typeof argInfo.defaultValue === 'undefined' ? '' :
|
|
1322
|
+
xmlEscape(maybeFormatMessage(argInfo.defaultValue, this.makeMessageContextForTarget()).toString());
|
|
1444
1323
|
|
|
1445
1324
|
if (argTypeInfo.check) {
|
|
1446
1325
|
// Right now the only type of 'check' we have specifies that the
|
|
@@ -1456,10 +1335,7 @@ class Runtime extends EventEmitter {
|
|
|
1456
1335
|
const menuInfo = context.categoryInfo.menuInfo[argInfo.menu];
|
|
1457
1336
|
if (menuInfo.acceptReporters) {
|
|
1458
1337
|
valueName = placeholder;
|
|
1459
|
-
shadowType = this._makeExtensionMenuId(
|
|
1460
|
-
argInfo.menu,
|
|
1461
|
-
context.categoryInfo.id
|
|
1462
|
-
);
|
|
1338
|
+
shadowType = this._makeExtensionMenuId(argInfo.menu, context.categoryInfo.id);
|
|
1463
1339
|
fieldName = argInfo.menu;
|
|
1464
1340
|
} else {
|
|
1465
1341
|
argJSON.type = 'field_dropdown';
|
|
@@ -1470,11 +1346,8 @@ class Runtime extends EventEmitter {
|
|
|
1470
1346
|
}
|
|
1471
1347
|
} else {
|
|
1472
1348
|
valueName = placeholder;
|
|
1473
|
-
shadowType =
|
|
1474
|
-
|
|
1475
|
-
fieldName =
|
|
1476
|
-
(argTypeInfo.shadow && argTypeInfo.shadow.fieldName) ||
|
|
1477
|
-
null;
|
|
1349
|
+
shadowType = (argTypeInfo.shadow && argTypeInfo.shadow.type) || null;
|
|
1350
|
+
fieldName = (argTypeInfo.shadow && argTypeInfo.shadow.fieldName) || null;
|
|
1478
1351
|
}
|
|
1479
1352
|
|
|
1480
1353
|
// <value> is the ScratchBlocks name for a block input.
|
|
@@ -1491,9 +1364,7 @@ class Runtime extends EventEmitter {
|
|
|
1491
1364
|
// A <field> displays a dynamic value: a user-editable text field, a drop-down menu, etc.
|
|
1492
1365
|
// Leave out the field if defaultValue or fieldName are not specified
|
|
1493
1366
|
if (defaultValue && fieldName) {
|
|
1494
|
-
context.inputList.push(
|
|
1495
|
-
`<field name="${fieldName}">${defaultValue}</field>`
|
|
1496
|
-
);
|
|
1367
|
+
context.inputList.push(`<field name="${fieldName}">${defaultValue}</field>`);
|
|
1497
1368
|
}
|
|
1498
1369
|
|
|
1499
1370
|
if (shadowType) {
|
|
@@ -1506,8 +1377,7 @@ class Runtime extends EventEmitter {
|
|
|
1506
1377
|
}
|
|
1507
1378
|
|
|
1508
1379
|
const argsName = `args${context.outLineNum}`;
|
|
1509
|
-
const blockArgs = (context.blockJSON[argsName] =
|
|
1510
|
-
context.blockJSON[argsName] || []);
|
|
1380
|
+
const blockArgs = (context.blockJSON[argsName] = context.blockJSON[argsName] || []);
|
|
1511
1381
|
if (argJSON) blockArgs.push(argJSON);
|
|
1512
1382
|
const argNum = blockArgs.length;
|
|
1513
1383
|
context.argsMap[placeholder] = argNum;
|
|
@@ -1549,7 +1419,8 @@ class Runtime extends EventEmitter {
|
|
|
1549
1419
|
} else if (categoryInfo.blockIconURI) {
|
|
1550
1420
|
menuIconURI = categoryInfo.blockIconURI;
|
|
1551
1421
|
}
|
|
1552
|
-
const menuIconXML = menuIconURI ?
|
|
1422
|
+
const menuIconXML = menuIconURI ?
|
|
1423
|
+
`iconURI="${menuIconURI}"` : '';
|
|
1553
1424
|
|
|
1554
1425
|
let statusButtonXML = '';
|
|
1555
1426
|
if (categoryInfo.showStatusButton) {
|
|
@@ -1558,11 +1429,8 @@ class Runtime extends EventEmitter {
|
|
|
1558
1429
|
|
|
1559
1430
|
return {
|
|
1560
1431
|
id: categoryInfo.id,
|
|
1561
|
-
xml: `<category name="${name}"
|
|
1562
|
-
|
|
1563
|
-
}" ${statusButtonXML} ${colorXML} ${menuIconXML}>${paletteBlocks
|
|
1564
|
-
.map(block => block.xml)
|
|
1565
|
-
.join('')}</category>`
|
|
1432
|
+
xml: `<category name="${name}" id="${categoryInfo.id}" ${statusButtonXML} ${colorXML} ${menuIconXML}>${
|
|
1433
|
+
paletteBlocks.map(block => block.xml).join('')}</category>`
|
|
1566
1434
|
};
|
|
1567
1435
|
});
|
|
1568
1436
|
}
|
|
@@ -1572,12 +1440,7 @@ class Runtime extends EventEmitter {
|
|
|
1572
1440
|
*/
|
|
1573
1441
|
getBlocksJSON () {
|
|
1574
1442
|
return this._blockInfo.reduce(
|
|
1575
|
-
(result, categoryInfo) =>
|
|
1576
|
-
result.concat(
|
|
1577
|
-
categoryInfo.blocks.map(blockInfo => blockInfo.json)
|
|
1578
|
-
),
|
|
1579
|
-
[]
|
|
1580
|
-
);
|
|
1443
|
+
(result, categoryInfo) => result.concat(categoryInfo.blocks.map(blockInfo => blockInfo.json)), []);
|
|
1581
1444
|
}
|
|
1582
1445
|
|
|
1583
1446
|
/**
|
|
@@ -1586,8 +1449,7 @@ class Runtime extends EventEmitter {
|
|
|
1586
1449
|
_initScratchLink () {
|
|
1587
1450
|
// Check that we're actually in a real browser, not Node.js or JSDOM, and we have a valid-looking origin.
|
|
1588
1451
|
// note that `if (self?....)` will throw if `self` is undefined, so check for that first!
|
|
1589
|
-
if (
|
|
1590
|
-
typeof self !== 'undefined' &&
|
|
1452
|
+
if (typeof self !== 'undefined' &&
|
|
1591
1453
|
typeof document !== 'undefined' &&
|
|
1592
1454
|
document.getElementById &&
|
|
1593
1455
|
self.origin &&
|
|
@@ -1600,9 +1462,7 @@ class Runtime extends EventEmitter {
|
|
|
1600
1462
|
)
|
|
1601
1463
|
) {
|
|
1602
1464
|
// Create a script tag for the Scratch Link browser extension, unless one already exists
|
|
1603
|
-
const scriptElement = document.getElementById(
|
|
1604
|
-
'scratch-link-extension-script'
|
|
1605
|
-
);
|
|
1465
|
+
const scriptElement = document.getElementById('scratch-link-extension-script');
|
|
1606
1466
|
if (!scriptElement) {
|
|
1607
1467
|
const script = document.createElement('script');
|
|
1608
1468
|
script.id = 'scratch-link-extension-script';
|
|
@@ -1621,8 +1481,7 @@ class Runtime extends EventEmitter {
|
|
|
1621
1481
|
* @returns {ScratchLinkSocket} The scratch link socket.
|
|
1622
1482
|
*/
|
|
1623
1483
|
getScratchLinkSocket (type) {
|
|
1624
|
-
const factory =
|
|
1625
|
-
this._linkSocketFactory || this._defaultScratchLinkSocketFactory;
|
|
1484
|
+
const factory = this._linkSocketFactory || this._defaultScratchLinkSocketFactory;
|
|
1626
1485
|
return factory(type);
|
|
1627
1486
|
}
|
|
1628
1487
|
|
|
@@ -1642,15 +1501,10 @@ class Runtime extends EventEmitter {
|
|
|
1642
1501
|
*/
|
|
1643
1502
|
_defaultScratchLinkSocketFactory (type) {
|
|
1644
1503
|
const Scratch = self.Scratch;
|
|
1645
|
-
const ScratchLinkSafariSocket =
|
|
1646
|
-
Scratch && Scratch.ScratchLinkSafariSocket;
|
|
1504
|
+
const ScratchLinkSafariSocket = Scratch && Scratch.ScratchLinkSafariSocket;
|
|
1647
1505
|
// detect this every time in case the user turns on the extension after loading the page
|
|
1648
|
-
const useSafariSocket =
|
|
1649
|
-
|
|
1650
|
-
ScratchLinkSafariSocket.isSafariHelperCompatible();
|
|
1651
|
-
return useSafariSocket ?
|
|
1652
|
-
new ScratchLinkSafariSocket(type) :
|
|
1653
|
-
new ScratchLinkWebSocket(type);
|
|
1506
|
+
const useSafariSocket = ScratchLinkSafariSocket && ScratchLinkSafariSocket.isSafariHelperCompatible();
|
|
1507
|
+
return useSafariSocket ? new ScratchLinkSafariSocket(type) : new ScratchLinkWebSocket(type);
|
|
1654
1508
|
}
|
|
1655
1509
|
|
|
1656
1510
|
/**
|
|
@@ -1739,12 +1593,11 @@ class Runtime extends EventEmitter {
|
|
|
1739
1593
|
* @return {boolean} True if the op is known to be a edge-activated hat.
|
|
1740
1594
|
*/
|
|
1741
1595
|
getIsEdgeActivatedHat (opcode) {
|
|
1742
|
-
return (
|
|
1743
|
-
|
|
1744
|
-
this._hats[opcode].edgeActivated
|
|
1745
|
-
);
|
|
1596
|
+
return Object.prototype.hasOwnProperty.call(this._hats, opcode) &&
|
|
1597
|
+
this._hats[opcode].edgeActivated;
|
|
1746
1598
|
}
|
|
1747
1599
|
|
|
1600
|
+
|
|
1748
1601
|
/**
|
|
1749
1602
|
* Attach the audio engine
|
|
1750
1603
|
* @param {!AudioEngine} audioEngine The audio engine to attach
|
|
@@ -1848,10 +1701,10 @@ class Runtime extends EventEmitter {
|
|
|
1848
1701
|
*/
|
|
1849
1702
|
isActiveThread (thread) {
|
|
1850
1703
|
return (
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1704
|
+
(
|
|
1705
|
+
thread.stack.length > 0 &&
|
|
1706
|
+
thread.status !== Thread.STATUS_DONE) &&
|
|
1707
|
+
this.threads.indexOf(thread) > -1);
|
|
1855
1708
|
}
|
|
1856
1709
|
|
|
1857
1710
|
/**
|
|
@@ -1876,29 +1729,18 @@ class Runtime extends EventEmitter {
|
|
|
1876
1729
|
* determines whether we show a visual report when turning on the script.
|
|
1877
1730
|
*/
|
|
1878
1731
|
toggleScript (topBlockId, opts) {
|
|
1879
|
-
opts = Object.assign(
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
},
|
|
1884
|
-
opts
|
|
1885
|
-
);
|
|
1732
|
+
opts = Object.assign({
|
|
1733
|
+
target: this._editingTarget,
|
|
1734
|
+
stackClick: false
|
|
1735
|
+
}, opts);
|
|
1886
1736
|
// Remove any existing thread.
|
|
1887
1737
|
for (let i = 0; i < this.threads.length; i++) {
|
|
1888
1738
|
// Toggling a script that's already running turns it off
|
|
1889
|
-
if (
|
|
1890
|
-
this.threads[i].topBlock === topBlockId &&
|
|
1891
|
-
this.threads[i].status !== Thread.STATUS_DONE
|
|
1892
|
-
) {
|
|
1739
|
+
if (this.threads[i].topBlock === topBlockId && this.threads[i].status !== Thread.STATUS_DONE) {
|
|
1893
1740
|
const blockContainer = opts.target.blocks;
|
|
1894
|
-
const opcode = blockContainer.getOpcode(
|
|
1895
|
-
blockContainer.getBlock(topBlockId)
|
|
1896
|
-
);
|
|
1741
|
+
const opcode = blockContainer.getOpcode(blockContainer.getBlock(topBlockId));
|
|
1897
1742
|
|
|
1898
|
-
if (
|
|
1899
|
-
this.getIsEdgeActivatedHat(opcode) &&
|
|
1900
|
-
this.threads[i].stackClick !== opts.stackClick
|
|
1901
|
-
) {
|
|
1743
|
+
if (this.getIsEdgeActivatedHat(opcode) && this.threads[i].stackClick !== opts.stackClick) {
|
|
1902
1744
|
// Allow edge activated hat thread stack click to coexist with
|
|
1903
1745
|
// edge activated hat thread that runs every frame
|
|
1904
1746
|
continue;
|
|
@@ -1920,11 +1762,8 @@ class Runtime extends EventEmitter {
|
|
|
1920
1762
|
if (!optTarget) optTarget = this._editingTarget;
|
|
1921
1763
|
for (let i = 0; i < this.threads.length; i++) {
|
|
1922
1764
|
// Don't re-add the script if it's already running
|
|
1923
|
-
if (
|
|
1924
|
-
|
|
1925
|
-
this.threads[i].status !== Thread.STATUS_DONE &&
|
|
1926
|
-
this.threads[i].updateMonitor
|
|
1927
|
-
) {
|
|
1765
|
+
if (this.threads[i].topBlock === topBlockId && this.threads[i].status !== Thread.STATUS_DONE &&
|
|
1766
|
+
this.threads[i].updateMonitor) {
|
|
1928
1767
|
return;
|
|
1929
1768
|
}
|
|
1930
1769
|
}
|
|
@@ -1962,10 +1801,7 @@ class Runtime extends EventEmitter {
|
|
|
1962
1801
|
}
|
|
1963
1802
|
for (let t = targets.length - 1; t >= 0; t--) {
|
|
1964
1803
|
const target = targets[t];
|
|
1965
|
-
const scripts = BlocksRuntimeCache.getScripts(
|
|
1966
|
-
target.blocks,
|
|
1967
|
-
opcode
|
|
1968
|
-
);
|
|
1804
|
+
const scripts = BlocksRuntimeCache.getScripts(target.blocks, opcode);
|
|
1969
1805
|
for (let j = 0; j < scripts.length; j++) {
|
|
1970
1806
|
f(scripts[j], target);
|
|
1971
1807
|
}
|
|
@@ -1979,13 +1815,9 @@ class Runtime extends EventEmitter {
|
|
|
1979
1815
|
* @param {Target=} optTarget Optionally, a target to restrict to.
|
|
1980
1816
|
* @return {Array.<Thread>} List of threads started by this function.
|
|
1981
1817
|
*/
|
|
1982
|
-
startHats (requestedHatOpcode,
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
this._hats,
|
|
1986
|
-
requestedHatOpcode
|
|
1987
|
-
)
|
|
1988
|
-
) {
|
|
1818
|
+
startHats (requestedHatOpcode,
|
|
1819
|
+
optMatchFields, optTarget) {
|
|
1820
|
+
if (!Object.prototype.hasOwnProperty.call(this._hats, requestedHatOpcode)) {
|
|
1989
1821
|
// No known hat with this opcode.
|
|
1990
1822
|
return;
|
|
1991
1823
|
}
|
|
@@ -1995,71 +1827,58 @@ class Runtime extends EventEmitter {
|
|
|
1995
1827
|
const hatMeta = instance._hats[requestedHatOpcode];
|
|
1996
1828
|
|
|
1997
1829
|
for (const opts in optMatchFields) {
|
|
1998
|
-
if (!Object.prototype.hasOwnProperty.call(optMatchFields, opts))
|
|
1999
|
-
continue;
|
|
2000
|
-
}
|
|
1830
|
+
if (!Object.prototype.hasOwnProperty.call(optMatchFields, opts)) continue;
|
|
2001
1831
|
optMatchFields[opts] = optMatchFields[opts].toUpperCase();
|
|
2002
1832
|
}
|
|
2003
1833
|
|
|
2004
1834
|
// Consider all scripts, looking for hats with opcode `requestedHatOpcode`.
|
|
2005
|
-
this.allScriptsByOpcodeDo(
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
) {
|
|
2021
|
-
// Field mismatch.
|
|
2022
|
-
return;
|
|
2023
|
-
}
|
|
1835
|
+
this.allScriptsByOpcodeDo(requestedHatOpcode, (script, target) => {
|
|
1836
|
+
const {
|
|
1837
|
+
blockId: topBlockId,
|
|
1838
|
+
fieldsOfInputs: hatFields
|
|
1839
|
+
} = script;
|
|
1840
|
+
|
|
1841
|
+
// Match any requested fields.
|
|
1842
|
+
// For example: ensures that broadcasts match.
|
|
1843
|
+
// This needs to happen before the block is evaluated
|
|
1844
|
+
// (i.e., before the predicate can be run) because "broadcast and wait"
|
|
1845
|
+
// needs to have a precise collection of started threads.
|
|
1846
|
+
for (const matchField in optMatchFields) {
|
|
1847
|
+
if (hatFields[matchField].value !== optMatchFields[matchField]) {
|
|
1848
|
+
// Field mismatch.
|
|
1849
|
+
return;
|
|
2024
1850
|
}
|
|
1851
|
+
}
|
|
2025
1852
|
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
newThreads.push(
|
|
2037
|
-
this._restartThread(this.threads[i])
|
|
2038
|
-
);
|
|
2039
|
-
return;
|
|
2040
|
-
}
|
|
1853
|
+
if (hatMeta.restartExistingThreads) {
|
|
1854
|
+
// If `restartExistingThreads` is true, we should stop
|
|
1855
|
+
// any existing threads starting with the top block.
|
|
1856
|
+
for (let i = 0; i < this.threads.length; i++) {
|
|
1857
|
+
if (this.threads[i].target === target &&
|
|
1858
|
+
this.threads[i].topBlock === topBlockId &&
|
|
1859
|
+
// stack click threads and hat threads can coexist
|
|
1860
|
+
!this.threads[i].stackClick) {
|
|
1861
|
+
newThreads.push(this._restartThread(this.threads[i]));
|
|
1862
|
+
return;
|
|
2041
1863
|
}
|
|
2042
|
-
}
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
return;
|
|
2055
|
-
}
|
|
1864
|
+
}
|
|
1865
|
+
} else {
|
|
1866
|
+
// If `restartExistingThreads` is false, we should
|
|
1867
|
+
// give up if any threads with the top block are running.
|
|
1868
|
+
for (let j = 0; j < this.threads.length; j++) {
|
|
1869
|
+
if (this.threads[j].target === target &&
|
|
1870
|
+
this.threads[j].topBlock === topBlockId &&
|
|
1871
|
+
// stack click threads and hat threads can coexist
|
|
1872
|
+
!this.threads[j].stackClick &&
|
|
1873
|
+
this.threads[j].status !== Thread.STATUS_DONE) {
|
|
1874
|
+
// Some thread is already running.
|
|
1875
|
+
return;
|
|
2056
1876
|
}
|
|
2057
1877
|
}
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
);
|
|
1878
|
+
}
|
|
1879
|
+
// Start the thread with this top block.
|
|
1880
|
+
newThreads.push(this._pushThread(topBlockId, target));
|
|
1881
|
+
}, optTarget);
|
|
2063
1882
|
// For compatibility with Scratch 2, edge triggered hats need to be processed before
|
|
2064
1883
|
// threads are stepped. See ScratchRuntime.as for original implementation
|
|
2065
1884
|
newThreads.forEach(thread => {
|
|
@@ -2069,6 +1888,7 @@ class Runtime extends EventEmitter {
|
|
|
2069
1888
|
return newThreads;
|
|
2070
1889
|
}
|
|
2071
1890
|
|
|
1891
|
+
|
|
2072
1892
|
/**
|
|
2073
1893
|
* Dispose all targets. Return to clean state.
|
|
2074
1894
|
*/
|
|
@@ -2100,10 +1920,8 @@ class Runtime extends EventEmitter {
|
|
|
2100
1920
|
const newCloudDataManager = cloudDataManager();
|
|
2101
1921
|
this.hasCloudData = newCloudDataManager.hasCloudVariables;
|
|
2102
1922
|
this.canAddCloudVariable = newCloudDataManager.canAddCloudVariable;
|
|
2103
|
-
this.addCloudVariable =
|
|
2104
|
-
|
|
2105
|
-
this.removeCloudVariable =
|
|
2106
|
-
this._initializeRemoveCloudVariable(newCloudDataManager);
|
|
1923
|
+
this.addCloudVariable = this._initializeAddCloudVariable(newCloudDataManager);
|
|
1924
|
+
this.removeCloudVariable = this._initializeRemoveCloudVariable(newCloudDataManager);
|
|
2107
1925
|
}
|
|
2108
1926
|
|
|
2109
1927
|
/**
|
|
@@ -2135,10 +1953,7 @@ class Runtime extends EventEmitter {
|
|
|
2135
1953
|
newIndex = this.executableTargets.length;
|
|
2136
1954
|
}
|
|
2137
1955
|
if (newIndex <= 0) {
|
|
2138
|
-
if (
|
|
2139
|
-
this.executableTargets.length > 0 &&
|
|
2140
|
-
this.executableTargets[0].isStage
|
|
2141
|
-
) {
|
|
1956
|
+
if (this.executableTargets.length > 0 && this.executableTargets[0].isStage) {
|
|
2142
1957
|
newIndex = 1;
|
|
2143
1958
|
} else {
|
|
2144
1959
|
newIndex = 0;
|
|
@@ -2218,10 +2033,7 @@ class Runtime extends EventEmitter {
|
|
|
2218
2033
|
}
|
|
2219
2034
|
|
|
2220
2035
|
const newRunId = uuid.v1();
|
|
2221
|
-
this.storage.scratchFetch.setMetadata(
|
|
2222
|
-
this.storage.scratchFetch.RequestMetadata.RunId,
|
|
2223
|
-
newRunId
|
|
2224
|
-
);
|
|
2036
|
+
this.storage.scratchFetch.setMetadata(this.storage.scratchFetch.RequestMetadata.RunId, newRunId);
|
|
2225
2037
|
}
|
|
2226
2038
|
|
|
2227
2039
|
/**
|
|
@@ -2250,13 +2062,8 @@ class Runtime extends EventEmitter {
|
|
|
2250
2062
|
const newTargets = [];
|
|
2251
2063
|
for (let i = 0; i < this.targets.length; i++) {
|
|
2252
2064
|
this.targets[i].onStopAll();
|
|
2253
|
-
if (
|
|
2254
|
-
|
|
2255
|
-
this.targets[i],
|
|
2256
|
-
'isOriginal'
|
|
2257
|
-
) &&
|
|
2258
|
-
!this.targets[i].isOriginal
|
|
2259
|
-
) {
|
|
2065
|
+
if (Object.prototype.hasOwnProperty.call(this.targets[i], 'isOriginal') &&
|
|
2066
|
+
!this.targets[i].isOriginal) {
|
|
2260
2067
|
this.targets[i].dispose();
|
|
2261
2068
|
} else {
|
|
2262
2069
|
newTargets.push(this.targets[i]);
|
|
@@ -2290,9 +2097,7 @@ class Runtime extends EventEmitter {
|
|
|
2290
2097
|
|
|
2291
2098
|
// Find all edge-activated hats, and add them to threads to be evaluated.
|
|
2292
2099
|
for (const hatType in this._hats) {
|
|
2293
|
-
if (!Object.prototype.hasOwnProperty.call(this._hats, hatType))
|
|
2294
|
-
continue;
|
|
2295
|
-
}
|
|
2100
|
+
if (!Object.prototype.hasOwnProperty.call(this._hats, hatType)) continue;
|
|
2296
2101
|
const hat = this._hats[hatType];
|
|
2297
2102
|
if (hat.edgeActivated) {
|
|
2298
2103
|
this.startHats(hatType);
|
|
@@ -2302,9 +2107,7 @@ class Runtime extends EventEmitter {
|
|
|
2302
2107
|
this._pushMonitors();
|
|
2303
2108
|
if (this.profiler !== null) {
|
|
2304
2109
|
if (stepThreadsProfilerId === -1) {
|
|
2305
|
-
stepThreadsProfilerId = this.profiler.idByName(
|
|
2306
|
-
'Sequencer.stepThreads'
|
|
2307
|
-
);
|
|
2110
|
+
stepThreadsProfilerId = this.profiler.idByName('Sequencer.stepThreads');
|
|
2308
2111
|
}
|
|
2309
2112
|
this.profiler.start(stepThreadsProfilerId);
|
|
2310
2113
|
}
|
|
@@ -2316,10 +2119,8 @@ class Runtime extends EventEmitter {
|
|
|
2316
2119
|
// Add done threads so that even if a thread finishes within 1 frame, the green
|
|
2317
2120
|
// flag will still indicate that a script ran.
|
|
2318
2121
|
this._emitProjectRunStatus(
|
|
2319
|
-
this.threads.length +
|
|
2320
|
-
|
|
2321
|
-
this._getMonitorThreadCount([...this.threads, ...doneThreads])
|
|
2322
|
-
);
|
|
2122
|
+
this.threads.length + doneThreads.length -
|
|
2123
|
+
this._getMonitorThreadCount([...this.threads, ...doneThreads]));
|
|
2323
2124
|
// Store threads that completed this iteration for testing and other
|
|
2324
2125
|
// internal purposes.
|
|
2325
2126
|
this._lastStepDoneThreads = doneThreads;
|
|
@@ -2327,8 +2128,7 @@ class Runtime extends EventEmitter {
|
|
|
2327
2128
|
// @todo: Only render when this.redrawRequested or clones rendered.
|
|
2328
2129
|
if (this.profiler !== null) {
|
|
2329
2130
|
if (rendererDrawProfilerId === -1) {
|
|
2330
|
-
rendererDrawProfilerId =
|
|
2331
|
-
this.profiler.idByName('RenderWebGL.draw');
|
|
2131
|
+
rendererDrawProfilerId = this.profiler.idByName('RenderWebGL.draw');
|
|
2332
2132
|
}
|
|
2333
2133
|
this.profiler.start(rendererDrawProfilerId);
|
|
2334
2134
|
}
|
|
@@ -2339,10 +2139,7 @@ class Runtime extends EventEmitter {
|
|
|
2339
2139
|
}
|
|
2340
2140
|
|
|
2341
2141
|
if (this._refreshTargets) {
|
|
2342
|
-
this.emit(
|
|
2343
|
-
Runtime.TARGETS_UPDATE,
|
|
2344
|
-
false /* Don't emit project changed */
|
|
2345
|
-
);
|
|
2142
|
+
this.emit(Runtime.TARGETS_UPDATE, false /* Don't emit project changed */);
|
|
2346
2143
|
this._refreshTargets = false;
|
|
2347
2144
|
}
|
|
2348
2145
|
|
|
@@ -2429,12 +2226,12 @@ class Runtime extends EventEmitter {
|
|
|
2429
2226
|
if (target === this._editingTarget) {
|
|
2430
2227
|
const blockForThread = thread.blockGlowInFrame;
|
|
2431
2228
|
if (thread.requestScriptGlowInFrame || thread.stackClick) {
|
|
2432
|
-
let script =
|
|
2433
|
-
target.blocks.getTopLevelScript(blockForThread);
|
|
2229
|
+
let script = target.blocks.getTopLevelScript(blockForThread);
|
|
2434
2230
|
if (!script) {
|
|
2435
2231
|
// Attempt to find in flyout blocks.
|
|
2436
|
-
script =
|
|
2437
|
-
|
|
2232
|
+
script = this.flyoutBlocks.getTopLevelScript(
|
|
2233
|
+
blockForThread
|
|
2234
|
+
);
|
|
2438
2235
|
}
|
|
2439
2236
|
if (script) {
|
|
2440
2237
|
requestedGlowsThisFrame.push(script);
|
|
@@ -2552,8 +2349,7 @@ class Runtime extends EventEmitter {
|
|
|
2552
2349
|
*/
|
|
2553
2350
|
requestAddMonitor (monitor) {
|
|
2554
2351
|
const id = monitor.get('id');
|
|
2555
|
-
if (!this.requestUpdateMonitor(monitor)) {
|
|
2556
|
-
// update monitor if it exists in the state
|
|
2352
|
+
if (!this.requestUpdateMonitor(monitor)) { // update monitor if it exists in the state
|
|
2557
2353
|
// if the monitor did not exist in the state, add it
|
|
2558
2354
|
this._monitorState = this._monitorState.set(id, monitor);
|
|
2559
2355
|
}
|
|
@@ -2571,15 +2367,12 @@ class Runtime extends EventEmitter {
|
|
|
2571
2367
|
if (this._monitorState.has(id)) {
|
|
2572
2368
|
this._monitorState =
|
|
2573
2369
|
// Use mergeWith here to prevent undefined values from overwriting existing ones
|
|
2574
|
-
this._monitorState.set(
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
return next;
|
|
2581
|
-
}, monitor)
|
|
2582
|
-
);
|
|
2370
|
+
this._monitorState.set(id, this._monitorState.get(id).mergeWith((prev, next) => {
|
|
2371
|
+
if (typeof next === 'undefined' || next === null) {
|
|
2372
|
+
return prev;
|
|
2373
|
+
}
|
|
2374
|
+
return next;
|
|
2375
|
+
}, monitor));
|
|
2583
2376
|
return true;
|
|
2584
2377
|
}
|
|
2585
2378
|
return false;
|
|
@@ -2600,12 +2393,10 @@ class Runtime extends EventEmitter {
|
|
|
2600
2393
|
* @return {boolean} true if monitor exists and was updated, false otherwise
|
|
2601
2394
|
*/
|
|
2602
2395
|
requestHideMonitor (monitorId) {
|
|
2603
|
-
return this.requestUpdateMonitor(
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
])
|
|
2608
|
-
);
|
|
2396
|
+
return this.requestUpdateMonitor(new Map([
|
|
2397
|
+
['id', monitorId],
|
|
2398
|
+
['visible', false]
|
|
2399
|
+
]));
|
|
2609
2400
|
}
|
|
2610
2401
|
|
|
2611
2402
|
/**
|
|
@@ -2615,12 +2406,10 @@ class Runtime extends EventEmitter {
|
|
|
2615
2406
|
* @return {boolean} true if monitor exists and was updated, false otherwise
|
|
2616
2407
|
*/
|
|
2617
2408
|
requestShowMonitor (monitorId) {
|
|
2618
|
-
return this.requestUpdateMonitor(
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
])
|
|
2623
|
-
);
|
|
2409
|
+
return this.requestUpdateMonitor(new Map([
|
|
2410
|
+
['id', monitorId],
|
|
2411
|
+
['visible', true]
|
|
2412
|
+
]));
|
|
2624
2413
|
}
|
|
2625
2414
|
|
|
2626
2415
|
/**
|
|
@@ -2629,9 +2418,7 @@ class Runtime extends EventEmitter {
|
|
|
2629
2418
|
* @param {!string} targetId Remove all monitors with given target ID.
|
|
2630
2419
|
*/
|
|
2631
2420
|
requestRemoveMonitorByTargetId (targetId) {
|
|
2632
|
-
this._monitorState = this._monitorState.filterNot(
|
|
2633
|
-
value => value.targetId === targetId
|
|
2634
|
-
);
|
|
2421
|
+
this._monitorState = this._monitorState.filterNot(value => value.targetId === targetId);
|
|
2635
2422
|
}
|
|
2636
2423
|
|
|
2637
2424
|
/**
|
|
@@ -2751,10 +2538,7 @@ class Runtime extends EventEmitter {
|
|
|
2751
2538
|
getAllVarNamesOfType (varType) {
|
|
2752
2539
|
let varNames = [];
|
|
2753
2540
|
for (const target of this.targets) {
|
|
2754
|
-
const targetVarNames = target.getAllVariableNamesInScopeByType(
|
|
2755
|
-
varType,
|
|
2756
|
-
true
|
|
2757
|
-
);
|
|
2541
|
+
const targetVarNames = target.getAllVariableNamesInScopeByType(varType, true);
|
|
2758
2542
|
varNames = varNames.concat(targetVarNames);
|
|
2759
2543
|
}
|
|
2760
2544
|
return varNames;
|
|
@@ -2795,8 +2579,7 @@ class Runtime extends EventEmitter {
|
|
|
2795
2579
|
* @return {Variable} The new variable that was created.
|
|
2796
2580
|
*/
|
|
2797
2581
|
createNewGlobalVariable (variableName, optVarId, optVarType) {
|
|
2798
|
-
const varType =
|
|
2799
|
-
typeof optVarType === 'string' ? optVarType : Variable.SCALAR_TYPE;
|
|
2582
|
+
const varType = (typeof optVarType === 'string') ? optVarType : Variable.SCALAR_TYPE;
|
|
2800
2583
|
const allVariableNames = this.getAllVarNamesOfType(varType);
|
|
2801
2584
|
const newName = StringUtil.unusedName(variableName, allVariableNames);
|
|
2802
2585
|
const variable = new Variable(optVarId || uid(), newName, varType);
|