scratch-vm 4.8.32 → 4.8.33
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/CHANGELOG.md +13 -0
- package/dist/node/scratch-vm.js +28 -14
- package/dist/node/scratch-vm.js.map +1 -1
- package/dist/web/scratch-vm.js +28 -14
- package/dist/web/scratch-vm.js.map +1 -1
- package/package.json +8 -8
- package/src/engine/blocks.js +297 -109
- package/src/engine/runtime.js +398 -177
package/src/engine/blocks.js
CHANGED
|
@@ -45,7 +45,10 @@ class Blocks {
|
|
|
45
45
|
* @type {{inputs: {}, procedureParamNames: {}, procedureDefinitions: {}}}
|
|
46
46
|
* @private
|
|
47
47
|
*/
|
|
48
|
-
Object.defineProperty(this, '_cache', {
|
|
48
|
+
Object.defineProperty(this, '_cache', {
|
|
49
|
+
writable: true,
|
|
50
|
+
enumerable: false
|
|
51
|
+
});
|
|
49
52
|
this._cache = {
|
|
50
53
|
/**
|
|
51
54
|
* Cache block inputs by block id
|
|
@@ -123,13 +126,13 @@ class Blocks {
|
|
|
123
126
|
}
|
|
124
127
|
|
|
125
128
|
/**
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
* Get the next block for a particular block
|
|
130
|
+
* @param {?string} id ID of block to get the next block for
|
|
131
|
+
* @return {?string} ID of next block in the sequence
|
|
132
|
+
*/
|
|
130
133
|
getNextBlock (id) {
|
|
131
134
|
const block = this._blocks[id];
|
|
132
|
-
return
|
|
135
|
+
return typeof block === 'undefined' ? null : block.next;
|
|
133
136
|
}
|
|
134
137
|
|
|
135
138
|
/**
|
|
@@ -150,7 +153,7 @@ class Blocks {
|
|
|
150
153
|
|
|
151
154
|
// Empty C-block?
|
|
152
155
|
const input = block.inputs[inputName];
|
|
153
|
-
return
|
|
156
|
+
return typeof input === 'undefined' ? null : input.block;
|
|
154
157
|
}
|
|
155
158
|
|
|
156
159
|
/**
|
|
@@ -159,7 +162,7 @@ class Blocks {
|
|
|
159
162
|
* @return {?string} the opcode corresponding to that block
|
|
160
163
|
*/
|
|
161
164
|
getOpcode (block) {
|
|
162
|
-
return
|
|
165
|
+
return typeof block === 'undefined' ? null : block.opcode;
|
|
163
166
|
}
|
|
164
167
|
|
|
165
168
|
/**
|
|
@@ -168,7 +171,7 @@ class Blocks {
|
|
|
168
171
|
* @return {?object} All fields and their values.
|
|
169
172
|
*/
|
|
170
173
|
getFields (block) {
|
|
171
|
-
return
|
|
174
|
+
return typeof block === 'undefined' ? null : block.fields;
|
|
172
175
|
}
|
|
173
176
|
|
|
174
177
|
/**
|
|
@@ -186,8 +189,10 @@ class Blocks {
|
|
|
186
189
|
inputs = {};
|
|
187
190
|
for (const input in block.inputs) {
|
|
188
191
|
// Ignore blocks prefixed with branch prefix.
|
|
189
|
-
if (
|
|
190
|
-
Blocks.BRANCH_INPUT_PREFIX)
|
|
192
|
+
if (
|
|
193
|
+
input.substring(0, Blocks.BRANCH_INPUT_PREFIX.length) !==
|
|
194
|
+
Blocks.BRANCH_INPUT_PREFIX
|
|
195
|
+
) {
|
|
191
196
|
inputs[input] = block.inputs[input];
|
|
192
197
|
}
|
|
193
198
|
}
|
|
@@ -202,7 +207,7 @@ class Blocks {
|
|
|
202
207
|
* @return {?object} Mutation for the block.
|
|
203
208
|
*/
|
|
204
209
|
getMutation (block) {
|
|
205
|
-
return
|
|
210
|
+
return typeof block === 'undefined' ? null : block.mutation;
|
|
206
211
|
}
|
|
207
212
|
|
|
208
213
|
/**
|
|
@@ -231,7 +236,9 @@ class Blocks {
|
|
|
231
236
|
}
|
|
232
237
|
|
|
233
238
|
for (const id in this._blocks) {
|
|
234
|
-
if (!Object.prototype.hasOwnProperty.call(this._blocks, id))
|
|
239
|
+
if (!Object.prototype.hasOwnProperty.call(this._blocks, id)) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
235
242
|
const block = this._blocks[id];
|
|
236
243
|
if (block.opcode === 'procedures_definition') {
|
|
237
244
|
const internal = this._getCustomBlockInternal(block);
|
|
@@ -267,10 +274,14 @@ class Blocks {
|
|
|
267
274
|
}
|
|
268
275
|
|
|
269
276
|
for (const id in this._blocks) {
|
|
270
|
-
if (!Object.prototype.hasOwnProperty.call(this._blocks, id))
|
|
277
|
+
if (!Object.prototype.hasOwnProperty.call(this._blocks, id)) {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
271
280
|
const block = this._blocks[id];
|
|
272
|
-
if (
|
|
273
|
-
block.
|
|
281
|
+
if (
|
|
282
|
+
block.opcode === 'procedures_prototype' &&
|
|
283
|
+
block.mutation.proccode === name
|
|
284
|
+
) {
|
|
274
285
|
const names = JSON.parse(block.mutation.argumentnames);
|
|
275
286
|
const ids = JSON.parse(block.mutation.argumentids);
|
|
276
287
|
const defaults = JSON.parse(block.mutation.argumentdefaults);
|
|
@@ -301,8 +312,11 @@ class Blocks {
|
|
|
301
312
|
blocklyListen (e) {
|
|
302
313
|
// Validate event
|
|
303
314
|
if (typeof e !== 'object') return;
|
|
304
|
-
if (
|
|
305
|
-
typeof e.
|
|
315
|
+
if (
|
|
316
|
+
typeof e.blockId !== 'string' &&
|
|
317
|
+
typeof e.varId !== 'string' &&
|
|
318
|
+
typeof e.commentId !== 'string'
|
|
319
|
+
) {
|
|
306
320
|
return;
|
|
307
321
|
}
|
|
308
322
|
const stage = this.runtime.getTargetForStage();
|
|
@@ -357,8 +371,13 @@ class Blocks {
|
|
|
357
371
|
case 'delete':
|
|
358
372
|
// Don't accept delete events for missing blocks,
|
|
359
373
|
// or shadow blocks being obscured.
|
|
360
|
-
if (
|
|
361
|
-
|
|
374
|
+
if (
|
|
375
|
+
!Object.prototype.hasOwnProperty.call(
|
|
376
|
+
this._blocks,
|
|
377
|
+
e.blockId
|
|
378
|
+
) ||
|
|
379
|
+
this._blocks[e.blockId].shadow
|
|
380
|
+
) {
|
|
362
381
|
return;
|
|
363
382
|
}
|
|
364
383
|
// Inform any runtime to forget about glows on this script.
|
|
@@ -375,9 +394,18 @@ class Blocks {
|
|
|
375
394
|
// into a state where a local var was requested for the stage,
|
|
376
395
|
// create a stage (global) var after checking for name conflicts
|
|
377
396
|
// on all the sprites.
|
|
378
|
-
if (
|
|
397
|
+
if (
|
|
398
|
+
e.isLocal &&
|
|
399
|
+
editingTarget &&
|
|
400
|
+
!editingTarget.isStage &&
|
|
401
|
+
!e.isCloud
|
|
402
|
+
) {
|
|
379
403
|
if (!editingTarget.lookupVariableById(e.varId)) {
|
|
380
|
-
editingTarget.createVariable(
|
|
404
|
+
editingTarget.createVariable(
|
|
405
|
+
e.varId,
|
|
406
|
+
e.varName,
|
|
407
|
+
e.varType
|
|
408
|
+
);
|
|
381
409
|
this.emitProjectChanged();
|
|
382
410
|
}
|
|
383
411
|
} else {
|
|
@@ -386,23 +414,45 @@ class Blocks {
|
|
|
386
414
|
return;
|
|
387
415
|
}
|
|
388
416
|
// Check for name conflicts in all of the targets
|
|
389
|
-
const allTargets = this.runtime.targets.filter(
|
|
417
|
+
const allTargets = this.runtime.targets.filter(
|
|
418
|
+
t => t.isOriginal
|
|
419
|
+
);
|
|
390
420
|
for (const target of allTargets) {
|
|
391
|
-
if (
|
|
421
|
+
if (
|
|
422
|
+
target.lookupVariableByNameAndType(
|
|
423
|
+
e.varName,
|
|
424
|
+
e.varType,
|
|
425
|
+
true
|
|
426
|
+
)
|
|
427
|
+
) {
|
|
392
428
|
return;
|
|
393
429
|
}
|
|
394
430
|
}
|
|
395
|
-
stage.createVariable(
|
|
431
|
+
stage.createVariable(
|
|
432
|
+
e.varId,
|
|
433
|
+
e.varName,
|
|
434
|
+
e.varType,
|
|
435
|
+
e.isCloud
|
|
436
|
+
);
|
|
396
437
|
this.emitProjectChanged();
|
|
397
438
|
}
|
|
398
439
|
break;
|
|
399
440
|
case 'var_rename':
|
|
400
|
-
if (
|
|
441
|
+
if (
|
|
442
|
+
editingTarget &&
|
|
443
|
+
Object.prototype.hasOwnProperty.call(
|
|
444
|
+
editingTarget.variables,
|
|
445
|
+
e.varId
|
|
446
|
+
)
|
|
447
|
+
) {
|
|
401
448
|
// This is a local variable, rename on the current target
|
|
402
449
|
editingTarget.renameVariable(e.varId, e.newName);
|
|
403
450
|
// Update all the blocks on the current target that use
|
|
404
451
|
// this variable
|
|
405
|
-
editingTarget.blocks.updateBlocksAfterVarRename(
|
|
452
|
+
editingTarget.blocks.updateBlocksAfterVarRename(
|
|
453
|
+
e.varId,
|
|
454
|
+
e.newName
|
|
455
|
+
);
|
|
406
456
|
} else {
|
|
407
457
|
// This is a global variable
|
|
408
458
|
stage.renameVariable(e.varId, e.newName);
|
|
@@ -410,14 +460,23 @@ class Blocks {
|
|
|
410
460
|
const targets = this.runtime.targets;
|
|
411
461
|
for (let i = 0; i < targets.length; i++) {
|
|
412
462
|
const currTarget = targets[i];
|
|
413
|
-
currTarget.blocks.updateBlocksAfterVarRename(
|
|
463
|
+
currTarget.blocks.updateBlocksAfterVarRename(
|
|
464
|
+
e.varId,
|
|
465
|
+
e.newName
|
|
466
|
+
);
|
|
414
467
|
}
|
|
415
468
|
}
|
|
416
469
|
this.emitProjectChanged();
|
|
417
470
|
break;
|
|
418
471
|
case 'var_delete': {
|
|
419
|
-
const target =
|
|
420
|
-
|
|
472
|
+
const target =
|
|
473
|
+
editingTarget &&
|
|
474
|
+
Object.prototype.hasOwnProperty.call(
|
|
475
|
+
editingTarget.variables,
|
|
476
|
+
e.varId
|
|
477
|
+
) ?
|
|
478
|
+
editingTarget :
|
|
479
|
+
stage;
|
|
421
480
|
target.deleteVariable(e.varId);
|
|
422
481
|
this.emitProjectChanged();
|
|
423
482
|
break;
|
|
@@ -425,11 +484,21 @@ class Blocks {
|
|
|
425
484
|
case 'comment_create':
|
|
426
485
|
if (this.runtime.getEditingTarget()) {
|
|
427
486
|
const currTarget = this.runtime.getEditingTarget();
|
|
428
|
-
currTarget.createComment(
|
|
429
|
-
e.
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
487
|
+
currTarget.createComment(
|
|
488
|
+
e.commentId,
|
|
489
|
+
e.blockId,
|
|
490
|
+
e.text,
|
|
491
|
+
e.xy.x,
|
|
492
|
+
e.xy.y,
|
|
493
|
+
e.width,
|
|
494
|
+
e.height,
|
|
495
|
+
e.minimized
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
if (
|
|
499
|
+
currTarget.comments[e.commentId].x === null &&
|
|
500
|
+
currTarget.comments[e.commentId].y === null
|
|
501
|
+
) {
|
|
433
502
|
// Block comments imported from 2.0 projects are imported with their
|
|
434
503
|
// x and y coordinates set to null so that scratch-blocks can
|
|
435
504
|
// auto-position them. If we are receiving a create event for these
|
|
@@ -445,8 +514,15 @@ class Blocks {
|
|
|
445
514
|
case 'comment_change':
|
|
446
515
|
if (this.runtime.getEditingTarget()) {
|
|
447
516
|
const currTarget = this.runtime.getEditingTarget();
|
|
448
|
-
if (
|
|
449
|
-
|
|
517
|
+
if (
|
|
518
|
+
!Object.prototype.hasOwnProperty.call(
|
|
519
|
+
currTarget.comments,
|
|
520
|
+
e.commentId
|
|
521
|
+
)
|
|
522
|
+
) {
|
|
523
|
+
log.warn(
|
|
524
|
+
`Cannot change comment with id ${e.commentId} because it does not exist.`
|
|
525
|
+
);
|
|
450
526
|
return;
|
|
451
527
|
}
|
|
452
528
|
const comment = currTarget.comments[e.commentId];
|
|
@@ -468,8 +544,16 @@ class Blocks {
|
|
|
468
544
|
case 'comment_move':
|
|
469
545
|
if (this.runtime.getEditingTarget()) {
|
|
470
546
|
const currTarget = this.runtime.getEditingTarget();
|
|
471
|
-
if (
|
|
472
|
-
|
|
547
|
+
if (
|
|
548
|
+
currTarget &&
|
|
549
|
+
!Object.prototype.hasOwnProperty.call(
|
|
550
|
+
currTarget.comments,
|
|
551
|
+
e.commentId
|
|
552
|
+
)
|
|
553
|
+
) {
|
|
554
|
+
log.warn(
|
|
555
|
+
`Cannot move comment with id ${e.commentId} because it does not exist.`
|
|
556
|
+
);
|
|
473
557
|
return;
|
|
474
558
|
}
|
|
475
559
|
const comment = currTarget.comments[e.commentId];
|
|
@@ -483,7 +567,12 @@ class Blocks {
|
|
|
483
567
|
case 'comment_delete':
|
|
484
568
|
if (this.runtime.getEditingTarget()) {
|
|
485
569
|
const currTarget = this.runtime.getEditingTarget();
|
|
486
|
-
if (
|
|
570
|
+
if (
|
|
571
|
+
!Object.prototype.hasOwnProperty.call(
|
|
572
|
+
currTarget.comments,
|
|
573
|
+
e.commentId
|
|
574
|
+
)
|
|
575
|
+
) {
|
|
487
576
|
// If we're in this state, we have probably received
|
|
488
577
|
// a delete event from a workspace that we switched from
|
|
489
578
|
// (e.g. a delete event for a comment on sprite a's workspace
|
|
@@ -494,7 +583,9 @@ class Blocks {
|
|
|
494
583
|
if (e.blockId) {
|
|
495
584
|
const block = currTarget.blocks.getBlock(e.blockId);
|
|
496
585
|
if (!block) {
|
|
497
|
-
log.warn(
|
|
586
|
+
log.warn(
|
|
587
|
+
`Could not find block referenced by comment with id: ${e.commentId}`
|
|
588
|
+
);
|
|
498
589
|
return;
|
|
499
590
|
}
|
|
500
591
|
delete block.comment;
|
|
@@ -562,7 +653,9 @@ class Blocks {
|
|
|
562
653
|
*/
|
|
563
654
|
changeBlock (args) {
|
|
564
655
|
// Validate
|
|
565
|
-
if (['field', 'mutation', 'checkbox'].indexOf(args.element) === -1)
|
|
656
|
+
if (['field', 'mutation', 'checkbox'].indexOf(args.element) === -1) {
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
566
659
|
let block = this._blocks[args.id];
|
|
567
660
|
if (typeof block === 'undefined') return;
|
|
568
661
|
switch (args.element) {
|
|
@@ -576,13 +669,17 @@ class Blocks {
|
|
|
576
669
|
// 3. the checkbox should become unchecked if we're not already
|
|
577
670
|
// monitoring current minute
|
|
578
671
|
|
|
579
|
-
|
|
580
672
|
// Update block value
|
|
581
673
|
if (!block.fields[args.name]) return;
|
|
582
|
-
if (
|
|
583
|
-
args.name === '
|
|
674
|
+
if (
|
|
675
|
+
args.name === 'VARIABLE' ||
|
|
676
|
+
args.name === 'LIST' ||
|
|
677
|
+
args.name === 'BROADCAST_OPTION'
|
|
678
|
+
) {
|
|
584
679
|
// Get variable name using the id in args.value.
|
|
585
|
-
const variable = this.runtime
|
|
680
|
+
const variable = this.runtime
|
|
681
|
+
.getEditingTarget()
|
|
682
|
+
.lookupVariableById(args.value);
|
|
586
683
|
if (variable) {
|
|
587
684
|
block.fields[args.name].value = variable.name;
|
|
588
685
|
block.fields[args.name].id = args.value;
|
|
@@ -596,19 +693,26 @@ class Blocks {
|
|
|
596
693
|
// TODO: (#1787)
|
|
597
694
|
if (block.opcode === 'sensing_of_object_menu') {
|
|
598
695
|
if (block.fields.OBJECT.value === '_stage_') {
|
|
599
|
-
this._blocks[block.parent].fields.PROPERTY.value =
|
|
696
|
+
this._blocks[block.parent].fields.PROPERTY.value =
|
|
697
|
+
'backdrop #';
|
|
600
698
|
} else {
|
|
601
|
-
this._blocks[block.parent].fields.PROPERTY.value =
|
|
699
|
+
this._blocks[block.parent].fields.PROPERTY.value =
|
|
700
|
+
'x position';
|
|
602
701
|
}
|
|
603
702
|
this.runtime.requestBlocksUpdate();
|
|
604
703
|
}
|
|
605
704
|
|
|
606
|
-
const flyoutBlock =
|
|
705
|
+
const flyoutBlock =
|
|
706
|
+
block.shadow && block.parent ?
|
|
707
|
+
this._blocks[block.parent] :
|
|
708
|
+
block;
|
|
607
709
|
if (flyoutBlock.isMonitored) {
|
|
608
|
-
this.runtime.requestUpdateMonitor(
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
710
|
+
this.runtime.requestUpdateMonitor(
|
|
711
|
+
Map({
|
|
712
|
+
id: flyoutBlock.id,
|
|
713
|
+
params: this._getBlockParams(flyoutBlock)
|
|
714
|
+
})
|
|
715
|
+
);
|
|
612
716
|
}
|
|
613
717
|
}
|
|
614
718
|
break;
|
|
@@ -619,14 +723,20 @@ class Blocks {
|
|
|
619
723
|
// A checkbox usually has a one to one correspondence with the monitor
|
|
620
724
|
// block but in the case of monitored reporters that have arguments,
|
|
621
725
|
// map the old id to a new id, creating a new monitor block if necessary
|
|
622
|
-
if (
|
|
623
|
-
block.
|
|
624
|
-
|
|
726
|
+
if (
|
|
727
|
+
block.fields &&
|
|
728
|
+
Object.keys(block.fields).length > 0 &&
|
|
729
|
+
block.opcode !== 'data_variable' &&
|
|
730
|
+
block.opcode !== 'data_listcontents'
|
|
731
|
+
) {
|
|
625
732
|
// This block has an argument which needs to get separated out into
|
|
626
733
|
// multiple monitor blocks with ids based on the selected argument
|
|
627
|
-
const newId = getMonitorIdForBlockWithArgs(block.id, block.fields);
|
|
628
734
|
// Note: we're not just constantly creating a longer and longer id everytime we check
|
|
629
735
|
// the checkbox because we're using the id of the block in the flyout as the base
|
|
736
|
+
const newId = getMonitorIdForBlockWithArgs(
|
|
737
|
+
block.id,
|
|
738
|
+
block.fields
|
|
739
|
+
);
|
|
630
740
|
|
|
631
741
|
// check if a block with the new id already exists, otherwise create
|
|
632
742
|
let newBlock = this.runtime.monitorBlocks.getBlock(newId);
|
|
@@ -645,19 +755,31 @@ class Blocks {
|
|
|
645
755
|
// Variable blocks may be sprite specific depending on the owner of the variable
|
|
646
756
|
let isSpriteLocalVariable = false;
|
|
647
757
|
if (block.opcode === 'data_variable') {
|
|
648
|
-
isSpriteLocalVariable =
|
|
758
|
+
isSpriteLocalVariable =
|
|
759
|
+
!this.runtime.getTargetForStage().variables[
|
|
760
|
+
block.fields.VARIABLE.id
|
|
761
|
+
];
|
|
649
762
|
} else if (block.opcode === 'data_listcontents') {
|
|
650
|
-
isSpriteLocalVariable =
|
|
763
|
+
isSpriteLocalVariable =
|
|
764
|
+
!this.runtime.getTargetForStage().variables[
|
|
765
|
+
block.fields.LIST.id
|
|
766
|
+
];
|
|
651
767
|
}
|
|
652
768
|
|
|
653
|
-
const isSpriteSpecific =
|
|
654
|
-
|
|
655
|
-
|
|
769
|
+
const isSpriteSpecific =
|
|
770
|
+
isSpriteLocalVariable ||
|
|
771
|
+
(Object.prototype.hasOwnProperty.call(
|
|
772
|
+
this.runtime.monitorBlockInfo,
|
|
773
|
+
block.opcode
|
|
774
|
+
) &&
|
|
775
|
+
this.runtime.monitorBlockInfo[block.opcode]
|
|
776
|
+
.isSpriteSpecific);
|
|
656
777
|
if (isSpriteSpecific) {
|
|
657
778
|
// If creating a new sprite specific monitor, the only possible target is
|
|
658
779
|
// the current editing one b/c you cannot dynamically create monitors.
|
|
659
780
|
// Also, do not change the targetId if it has already been assigned
|
|
660
|
-
block.targetId =
|
|
781
|
+
block.targetId =
|
|
782
|
+
block.targetId || this.runtime.getEditingTarget().id;
|
|
661
783
|
} else {
|
|
662
784
|
block.targetId = null;
|
|
663
785
|
}
|
|
@@ -667,16 +789,25 @@ class Blocks {
|
|
|
667
789
|
} else if (!wasMonitored && block.isMonitored) {
|
|
668
790
|
// Tries to show the monitor for specified block. If it doesn't exist, add the monitor.
|
|
669
791
|
if (!this.runtime.requestShowMonitor(block.id)) {
|
|
670
|
-
this.runtime.requestAddMonitor(
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
792
|
+
this.runtime.requestAddMonitor(
|
|
793
|
+
MonitorRecord({
|
|
794
|
+
id: block.id,
|
|
795
|
+
targetId: block.targetId,
|
|
796
|
+
spriteName: block.targetId ?
|
|
797
|
+
this.runtime
|
|
798
|
+
.getTargetById(block.targetId)
|
|
799
|
+
.getName() :
|
|
800
|
+
null,
|
|
801
|
+
opcode: block.opcode,
|
|
802
|
+
params: this._getBlockParams(block),
|
|
803
|
+
// @todo(vm#565) for numerical values with decimals, some countries use comma
|
|
804
|
+
value: '',
|
|
805
|
+
mode:
|
|
806
|
+
block.opcode === 'data_listcontents' ?
|
|
807
|
+
'list' :
|
|
808
|
+
'default'
|
|
809
|
+
})
|
|
810
|
+
);
|
|
680
811
|
}
|
|
681
812
|
}
|
|
682
813
|
break;
|
|
@@ -705,8 +836,8 @@ class Blocks {
|
|
|
705
836
|
|
|
706
837
|
// Move coordinate changes.
|
|
707
838
|
if (e.newCoordinate) {
|
|
708
|
-
|
|
709
|
-
|
|
839
|
+
didChange =
|
|
840
|
+
block.x !== e.newCoordinate.x || block.y !== e.newCoordinate.y;
|
|
710
841
|
|
|
711
842
|
block.x = e.newCoordinate.x;
|
|
712
843
|
block.y = e.newCoordinate.y;
|
|
@@ -715,8 +846,10 @@ class Blocks {
|
|
|
715
846
|
// Remove from any old parent.
|
|
716
847
|
if (typeof e.oldParent !== 'undefined') {
|
|
717
848
|
const oldParent = this._blocks[e.oldParent];
|
|
718
|
-
if (
|
|
719
|
-
|
|
849
|
+
if (
|
|
850
|
+
typeof e.oldInput !== 'undefined' &&
|
|
851
|
+
oldParent.inputs[e.oldInput].block === e.id
|
|
852
|
+
) {
|
|
720
853
|
// This block was connected to the old parent's input.
|
|
721
854
|
oldParent.inputs[e.oldInput].block = null;
|
|
722
855
|
} else if (oldParent.next === e.id) {
|
|
@@ -741,8 +874,14 @@ class Blocks {
|
|
|
741
874
|
// Moved to the new parent's input.
|
|
742
875
|
// Don't obscure the shadow block.
|
|
743
876
|
let oldShadow = null;
|
|
744
|
-
if (
|
|
745
|
-
|
|
877
|
+
if (
|
|
878
|
+
Object.prototype.hasOwnProperty.call(
|
|
879
|
+
this._blocks[e.newParent].inputs,
|
|
880
|
+
e.newInput
|
|
881
|
+
)
|
|
882
|
+
) {
|
|
883
|
+
oldShadow =
|
|
884
|
+
this._blocks[e.newParent].inputs[e.newInput].shadow;
|
|
746
885
|
}
|
|
747
886
|
|
|
748
887
|
// If the block being attached is itself a shadow, make sure to set
|
|
@@ -764,7 +903,6 @@ class Blocks {
|
|
|
764
903
|
if (didChange) this.emitProjectChanged();
|
|
765
904
|
}
|
|
766
905
|
|
|
767
|
-
|
|
768
906
|
/**
|
|
769
907
|
* Block management: run all blocks.
|
|
770
908
|
* @param {!object} runtime Runtime to run all blocks in.
|
|
@@ -777,7 +915,9 @@ class Blocks {
|
|
|
777
915
|
const targetId = this.getBlock(blockId).targetId;
|
|
778
916
|
return {
|
|
779
917
|
blockId,
|
|
780
|
-
target: targetId ?
|
|
918
|
+
target: targetId ?
|
|
919
|
+
runtime.getTargetById(targetId) :
|
|
920
|
+
null
|
|
781
921
|
};
|
|
782
922
|
});
|
|
783
923
|
}
|
|
@@ -816,8 +956,10 @@ class Blocks {
|
|
|
816
956
|
this.deleteBlock(block.inputs[input].block);
|
|
817
957
|
}
|
|
818
958
|
// Delete obscured shadow blocks.
|
|
819
|
-
if (
|
|
820
|
-
block.inputs[input].shadow !==
|
|
959
|
+
if (
|
|
960
|
+
block.inputs[input].shadow !== null &&
|
|
961
|
+
block.inputs[input].shadow !== block.inputs[input].block
|
|
962
|
+
) {
|
|
821
963
|
this.deleteBlock(block.inputs[input].shadow);
|
|
822
964
|
}
|
|
823
965
|
}
|
|
@@ -863,7 +1005,10 @@ class Blocks {
|
|
|
863
1005
|
} else if (blocks[blockId].fields.LIST) {
|
|
864
1006
|
varOrListField = blocks[blockId].fields.LIST;
|
|
865
1007
|
varType = Variable.LIST_TYPE;
|
|
866
|
-
} else if (
|
|
1008
|
+
} else if (
|
|
1009
|
+
optIncludeBroadcast &&
|
|
1010
|
+
blocks[blockId].fields.BROADCAST_OPTION
|
|
1011
|
+
) {
|
|
867
1012
|
varOrListField = blocks[blockId].fields.BROADCAST_OPTION;
|
|
868
1013
|
varType = Variable.BROADCAST_MESSAGE_TYPE;
|
|
869
1014
|
}
|
|
@@ -875,10 +1020,12 @@ class Blocks {
|
|
|
875
1020
|
type: varType
|
|
876
1021
|
});
|
|
877
1022
|
} else {
|
|
878
|
-
allReferences[currVarId] = [
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
1023
|
+
allReferences[currVarId] = [
|
|
1024
|
+
{
|
|
1025
|
+
referencingField: varOrListField,
|
|
1026
|
+
type: varType
|
|
1027
|
+
}
|
|
1028
|
+
];
|
|
882
1029
|
}
|
|
883
1030
|
}
|
|
884
1031
|
}
|
|
@@ -915,9 +1062,15 @@ class Blocks {
|
|
|
915
1062
|
updateTargetSpecificBlocks (isStage) {
|
|
916
1063
|
const blocks = this._blocks;
|
|
917
1064
|
for (const blockId in blocks) {
|
|
918
|
-
if (
|
|
1065
|
+
if (
|
|
1066
|
+
isStage &&
|
|
1067
|
+
blocks[blockId].opcode === 'event_whenthisspriteclicked'
|
|
1068
|
+
) {
|
|
919
1069
|
blocks[blockId].opcode = 'event_whenstageclicked';
|
|
920
|
-
} else if (
|
|
1070
|
+
} else if (
|
|
1071
|
+
!isStage &&
|
|
1072
|
+
blocks[blockId].opcode === 'event_whenstageclicked'
|
|
1073
|
+
) {
|
|
921
1074
|
blocks[blockId].opcode = 'event_whenthisspriteclicked';
|
|
922
1075
|
}
|
|
923
1076
|
}
|
|
@@ -967,10 +1120,12 @@ class Blocks {
|
|
|
967
1120
|
let blockUpdated = false;
|
|
968
1121
|
for (const blockId in blocks) {
|
|
969
1122
|
const block = blocks[blockId];
|
|
970
|
-
if (
|
|
1123
|
+
if (
|
|
1124
|
+
block.opcode === 'sensing_of' &&
|
|
971
1125
|
block.fields.PROPERTY.value === oldName &&
|
|
972
1126
|
// If block and shadow are different, it means a block is inserted to OBJECT, and should be ignored.
|
|
973
|
-
block.inputs.OBJECT.block === block.inputs.OBJECT.shadow
|
|
1127
|
+
block.inputs.OBJECT.block === block.inputs.OBJECT.shadow
|
|
1128
|
+
) {
|
|
974
1129
|
const inputBlock = this.getBlock(block.inputs.OBJECT.block);
|
|
975
1130
|
if (inputBlock.fields.OBJECT.value === targetName) {
|
|
976
1131
|
block.fields.PROPERTY.value = newName;
|
|
@@ -991,7 +1146,10 @@ class Blocks {
|
|
|
991
1146
|
*/
|
|
992
1147
|
_getCostumeField (blockId) {
|
|
993
1148
|
const block = this.getBlock(blockId);
|
|
994
|
-
if (
|
|
1149
|
+
if (
|
|
1150
|
+
block &&
|
|
1151
|
+
Object.prototype.hasOwnProperty.call(block.fields, 'COSTUME')
|
|
1152
|
+
) {
|
|
995
1153
|
return block.fields.COSTUME;
|
|
996
1154
|
}
|
|
997
1155
|
return null;
|
|
@@ -1006,7 +1164,10 @@ class Blocks {
|
|
|
1006
1164
|
*/
|
|
1007
1165
|
_getSoundField (blockId) {
|
|
1008
1166
|
const block = this.getBlock(blockId);
|
|
1009
|
-
if (
|
|
1167
|
+
if (
|
|
1168
|
+
block &&
|
|
1169
|
+
Object.prototype.hasOwnProperty.call(block.fields, 'SOUND_MENU')
|
|
1170
|
+
) {
|
|
1010
1171
|
return block.fields.SOUND_MENU;
|
|
1011
1172
|
}
|
|
1012
1173
|
return null;
|
|
@@ -1021,7 +1182,10 @@ class Blocks {
|
|
|
1021
1182
|
*/
|
|
1022
1183
|
_getBackdropField (blockId) {
|
|
1023
1184
|
const block = this.getBlock(blockId);
|
|
1024
|
-
if (
|
|
1185
|
+
if (
|
|
1186
|
+
block &&
|
|
1187
|
+
Object.prototype.hasOwnProperty.call(block.fields, 'BACKDROP')
|
|
1188
|
+
) {
|
|
1025
1189
|
return block.fields.BACKDROP;
|
|
1026
1190
|
}
|
|
1027
1191
|
return null;
|
|
@@ -1039,8 +1203,15 @@ class Blocks {
|
|
|
1039
1203
|
if (!block) {
|
|
1040
1204
|
return null;
|
|
1041
1205
|
}
|
|
1042
|
-
const spriteMenuNames = [
|
|
1043
|
-
'
|
|
1206
|
+
const spriteMenuNames = [
|
|
1207
|
+
'TOWARDS',
|
|
1208
|
+
'TO',
|
|
1209
|
+
'OBJECT',
|
|
1210
|
+
'VIDEOONMENU2',
|
|
1211
|
+
'DISTANCETOMENU',
|
|
1212
|
+
'TOUCHINGOBJECTMENU',
|
|
1213
|
+
'CLONE_OPTION'
|
|
1214
|
+
];
|
|
1044
1215
|
for (let i = 0; i < spriteMenuNames.length; i++) {
|
|
1045
1216
|
const menuName = spriteMenuNames[i];
|
|
1046
1217
|
if (Object.prototype.hasOwnProperty.call(block.fields, menuName)) {
|
|
@@ -1059,7 +1230,9 @@ class Blocks {
|
|
|
1059
1230
|
* @return {string} String of XML representing this object's blocks.
|
|
1060
1231
|
*/
|
|
1061
1232
|
toXML (comments) {
|
|
1062
|
-
return this._scripts
|
|
1233
|
+
return this._scripts
|
|
1234
|
+
.map(script => this.blockToXML(script, comments))
|
|
1235
|
+
.join();
|
|
1063
1236
|
}
|
|
1064
1237
|
|
|
1065
1238
|
/**
|
|
@@ -1076,9 +1249,8 @@ class Blocks {
|
|
|
1076
1249
|
// this early exit allows the project to load.
|
|
1077
1250
|
if (!block) return;
|
|
1078
1251
|
// Encode properties of this block.
|
|
1079
|
-
const tagName =
|
|
1080
|
-
let xmlString =
|
|
1081
|
-
`<${tagName}
|
|
1252
|
+
const tagName = block.shadow ? 'shadow' : 'block';
|
|
1253
|
+
let xmlString = `<${tagName}
|
|
1082
1254
|
id="${block.id}"
|
|
1083
1255
|
type="${block.opcode}"
|
|
1084
1256
|
${block.topLevel ? `x="${block.x}" y="${block.y}"` : ''}
|
|
@@ -1089,10 +1261,14 @@ class Blocks {
|
|
|
1089
1261
|
if (Object.prototype.hasOwnProperty.call(comments, commentId)) {
|
|
1090
1262
|
xmlString += comments[commentId].toXML();
|
|
1091
1263
|
} else {
|
|
1092
|
-
log.warn(
|
|
1264
|
+
log.warn(
|
|
1265
|
+
`Could not find comment with id: ${commentId} in provided comment descriptions.`
|
|
1266
|
+
);
|
|
1093
1267
|
}
|
|
1094
1268
|
} else {
|
|
1095
|
-
log.warn(
|
|
1269
|
+
log.warn(
|
|
1270
|
+
`Cannot serialize comment with id: ${commentId}; no comment descriptions provided.`
|
|
1271
|
+
);
|
|
1096
1272
|
}
|
|
1097
1273
|
}
|
|
1098
1274
|
// Add any mutation. Must come before inputs.
|
|
@@ -1101,7 +1277,9 @@ class Blocks {
|
|
|
1101
1277
|
}
|
|
1102
1278
|
// Add any inputs on this block.
|
|
1103
1279
|
for (const input in block.inputs) {
|
|
1104
|
-
if (!Object.prototype.hasOwnProperty.call(block.inputs, input))
|
|
1280
|
+
if (!Object.prototype.hasOwnProperty.call(block.inputs, input)) {
|
|
1281
|
+
continue;
|
|
1282
|
+
}
|
|
1105
1283
|
const blockInput = block.inputs[input];
|
|
1106
1284
|
// Only encode a value tag if the value input is occupied.
|
|
1107
1285
|
if (blockInput.block || blockInput.shadow) {
|
|
@@ -1109,7 +1287,10 @@ class Blocks {
|
|
|
1109
1287
|
if (blockInput.block) {
|
|
1110
1288
|
xmlString += this.blockToXML(blockInput.block, comments);
|
|
1111
1289
|
}
|
|
1112
|
-
if (
|
|
1290
|
+
if (
|
|
1291
|
+
blockInput.shadow &&
|
|
1292
|
+
blockInput.shadow !== blockInput.block
|
|
1293
|
+
) {
|
|
1113
1294
|
// Obscured shadow.
|
|
1114
1295
|
xmlString += this.blockToXML(blockInput.shadow, comments);
|
|
1115
1296
|
}
|
|
@@ -1118,7 +1299,9 @@ class Blocks {
|
|
|
1118
1299
|
}
|
|
1119
1300
|
// Add any fields on this block.
|
|
1120
1301
|
for (const field in block.fields) {
|
|
1121
|
-
if (!Object.prototype.hasOwnProperty.call(block.fields, field))
|
|
1302
|
+
if (!Object.prototype.hasOwnProperty.call(block.fields, field)) {
|
|
1303
|
+
continue;
|
|
1304
|
+
}
|
|
1122
1305
|
const blockField = block.fields[field];
|
|
1123
1306
|
xmlString += `<field name="${blockField.name}"`;
|
|
1124
1307
|
const fieldId = blockField.id;
|
|
@@ -1137,7 +1320,10 @@ class Blocks {
|
|
|
1137
1320
|
}
|
|
1138
1321
|
// Add blocks connected to the next connection.
|
|
1139
1322
|
if (block.next) {
|
|
1140
|
-
xmlString += `<next>${this.blockToXML(
|
|
1323
|
+
xmlString += `<next>${this.blockToXML(
|
|
1324
|
+
block.next,
|
|
1325
|
+
comments
|
|
1326
|
+
)}</next>`;
|
|
1141
1327
|
}
|
|
1142
1328
|
xmlString += `</${tagName}>`;
|
|
1143
1329
|
return xmlString;
|
|
@@ -1152,8 +1338,10 @@ class Blocks {
|
|
|
1152
1338
|
let mutationString = `<${mutation.tagName}`;
|
|
1153
1339
|
for (const prop in mutation) {
|
|
1154
1340
|
if (prop === 'children' || prop === 'tagName') continue;
|
|
1155
|
-
let mutationValue =
|
|
1156
|
-
|
|
1341
|
+
let mutationValue =
|
|
1342
|
+
typeof mutation[prop] === 'string' ?
|
|
1343
|
+
xmlEscape(mutation[prop]) :
|
|
1344
|
+
mutation[prop];
|
|
1157
1345
|
|
|
1158
1346
|
// Handle dynamic extension blocks
|
|
1159
1347
|
if (prop === 'blockInfo') {
|