mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-07-30 16:10:08 -04:00
Merge pull request #4966 from scratchfoundation/gonfunko-style
style: copy style changes from gonfunko/modern-blockly
This commit is contained in:
commit
afa7acc4a0
2 changed files with 693 additions and 284 deletions
src/engine
|
@ -45,7 +45,10 @@ class Blocks {
|
||||||
* @type {{inputs: {}, procedureParamNames: {}, procedureDefinitions: {}}}
|
* @type {{inputs: {}, procedureParamNames: {}, procedureDefinitions: {}}}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
Object.defineProperty(this, '_cache', {writable: true, enumerable: false});
|
Object.defineProperty(this, '_cache', {
|
||||||
|
writable: true,
|
||||||
|
enumerable: false
|
||||||
|
});
|
||||||
this._cache = {
|
this._cache = {
|
||||||
/**
|
/**
|
||||||
* Cache block inputs by block id
|
* Cache block inputs by block id
|
||||||
|
@ -129,7 +132,7 @@ class Blocks {
|
||||||
*/
|
*/
|
||||||
getNextBlock (id) {
|
getNextBlock (id) {
|
||||||
const block = this._blocks[id];
|
const block = this._blocks[id];
|
||||||
return (typeof block === 'undefined') ? null : block.next;
|
return typeof block === 'undefined' ? null : block.next;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -150,7 +153,7 @@ class Blocks {
|
||||||
|
|
||||||
// Empty C-block?
|
// Empty C-block?
|
||||||
const input = block.inputs[inputName];
|
const input = block.inputs[inputName];
|
||||||
return (typeof input === 'undefined') ? null : input.block;
|
return typeof input === 'undefined' ? null : input.block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -159,7 +162,7 @@ class Blocks {
|
||||||
* @return {?string} the opcode corresponding to that block
|
* @return {?string} the opcode corresponding to that block
|
||||||
*/
|
*/
|
||||||
getOpcode (block) {
|
getOpcode (block) {
|
||||||
return (typeof block === 'undefined') ? null : block.opcode;
|
return typeof block === 'undefined' ? null : block.opcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -168,7 +171,7 @@ class Blocks {
|
||||||
* @return {?object} All fields and their values.
|
* @return {?object} All fields and their values.
|
||||||
*/
|
*/
|
||||||
getFields (block) {
|
getFields (block) {
|
||||||
return (typeof block === 'undefined') ? null : block.fields;
|
return typeof block === 'undefined' ? null : block.fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -186,8 +189,10 @@ class Blocks {
|
||||||
inputs = {};
|
inputs = {};
|
||||||
for (const input in block.inputs) {
|
for (const input in block.inputs) {
|
||||||
// Ignore blocks prefixed with branch prefix.
|
// Ignore blocks prefixed with branch prefix.
|
||||||
if (input.substring(0, Blocks.BRANCH_INPUT_PREFIX.length) !==
|
if (
|
||||||
Blocks.BRANCH_INPUT_PREFIX) {
|
input.substring(0, Blocks.BRANCH_INPUT_PREFIX.length) !==
|
||||||
|
Blocks.BRANCH_INPUT_PREFIX
|
||||||
|
) {
|
||||||
inputs[input] = block.inputs[input];
|
inputs[input] = block.inputs[input];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,7 +207,7 @@ class Blocks {
|
||||||
* @return {?object} Mutation for the block.
|
* @return {?object} Mutation for the block.
|
||||||
*/
|
*/
|
||||||
getMutation (block) {
|
getMutation (block) {
|
||||||
return (typeof block === 'undefined') ? null : block.mutation;
|
return typeof block === 'undefined' ? null : block.mutation;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -231,7 +236,9 @@ class Blocks {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const id in this._blocks) {
|
for (const id in this._blocks) {
|
||||||
if (!Object.prototype.hasOwnProperty.call(this._blocks, id)) continue;
|
if (!Object.prototype.hasOwnProperty.call(this._blocks, id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const block = this._blocks[id];
|
const block = this._blocks[id];
|
||||||
if (block.opcode === 'procedures_definition') {
|
if (block.opcode === 'procedures_definition') {
|
||||||
const internal = this._getCustomBlockInternal(block);
|
const internal = this._getCustomBlockInternal(block);
|
||||||
|
@ -267,10 +274,14 @@ class Blocks {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const id in this._blocks) {
|
for (const id in this._blocks) {
|
||||||
if (!Object.prototype.hasOwnProperty.call(this._blocks, id)) continue;
|
if (!Object.prototype.hasOwnProperty.call(this._blocks, id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const block = this._blocks[id];
|
const block = this._blocks[id];
|
||||||
if (block.opcode === 'procedures_prototype' &&
|
if (
|
||||||
block.mutation.proccode === name) {
|
block.opcode === 'procedures_prototype' &&
|
||||||
|
block.mutation.proccode === name
|
||||||
|
) {
|
||||||
const names = JSON.parse(block.mutation.argumentnames);
|
const names = JSON.parse(block.mutation.argumentnames);
|
||||||
const ids = JSON.parse(block.mutation.argumentids);
|
const ids = JSON.parse(block.mutation.argumentids);
|
||||||
const defaults = JSON.parse(block.mutation.argumentdefaults);
|
const defaults = JSON.parse(block.mutation.argumentdefaults);
|
||||||
|
@ -301,8 +312,11 @@ class Blocks {
|
||||||
blocklyListen (e) {
|
blocklyListen (e) {
|
||||||
// Validate event
|
// Validate event
|
||||||
if (typeof e !== 'object') return;
|
if (typeof e !== 'object') return;
|
||||||
if (typeof e.blockId !== 'string' && typeof e.varId !== 'string' &&
|
if (
|
||||||
typeof e.commentId !== 'string') {
|
typeof e.blockId !== 'string' &&
|
||||||
|
typeof e.varId !== 'string' &&
|
||||||
|
typeof e.commentId !== 'string'
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const stage = this.runtime.getTargetForStage();
|
const stage = this.runtime.getTargetForStage();
|
||||||
|
@ -357,8 +371,13 @@ class Blocks {
|
||||||
case 'delete':
|
case 'delete':
|
||||||
// Don't accept delete events for missing blocks,
|
// Don't accept delete events for missing blocks,
|
||||||
// or shadow blocks being obscured.
|
// or shadow blocks being obscured.
|
||||||
if (!Object.prototype.hasOwnProperty.call(this._blocks, e.blockId) ||
|
if (
|
||||||
this._blocks[e.blockId].shadow) {
|
!Object.prototype.hasOwnProperty.call(
|
||||||
|
this._blocks,
|
||||||
|
e.blockId
|
||||||
|
) ||
|
||||||
|
this._blocks[e.blockId].shadow
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Inform any runtime to forget about glows on this script.
|
// Inform any runtime to forget about glows on this script.
|
||||||
|
@ -375,9 +394,18 @@ class Blocks {
|
||||||
// into a state where a local var was requested for the stage,
|
// into a state where a local var was requested for the stage,
|
||||||
// create a stage (global) var after checking for name conflicts
|
// create a stage (global) var after checking for name conflicts
|
||||||
// on all the sprites.
|
// on all the sprites.
|
||||||
if (e.isLocal && editingTarget && !editingTarget.isStage && !e.isCloud) {
|
if (
|
||||||
|
e.isLocal &&
|
||||||
|
editingTarget &&
|
||||||
|
!editingTarget.isStage &&
|
||||||
|
!e.isCloud
|
||||||
|
) {
|
||||||
if (!editingTarget.lookupVariableById(e.varId)) {
|
if (!editingTarget.lookupVariableById(e.varId)) {
|
||||||
editingTarget.createVariable(e.varId, e.varName, e.varType);
|
editingTarget.createVariable(
|
||||||
|
e.varId,
|
||||||
|
e.varName,
|
||||||
|
e.varType
|
||||||
|
);
|
||||||
this.emitProjectChanged();
|
this.emitProjectChanged();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -386,23 +414,45 @@ class Blocks {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Check for name conflicts in all of the targets
|
// Check for name conflicts in all of the targets
|
||||||
const allTargets = this.runtime.targets.filter(t => t.isOriginal);
|
const allTargets = this.runtime.targets.filter(
|
||||||
|
t => t.isOriginal
|
||||||
|
);
|
||||||
for (const target of allTargets) {
|
for (const target of allTargets) {
|
||||||
if (target.lookupVariableByNameAndType(e.varName, e.varType, true)) {
|
if (
|
||||||
|
target.lookupVariableByNameAndType(
|
||||||
|
e.varName,
|
||||||
|
e.varType,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage.createVariable(e.varId, e.varName, e.varType, e.isCloud);
|
stage.createVariable(
|
||||||
|
e.varId,
|
||||||
|
e.varName,
|
||||||
|
e.varType,
|
||||||
|
e.isCloud
|
||||||
|
);
|
||||||
this.emitProjectChanged();
|
this.emitProjectChanged();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'var_rename':
|
case 'var_rename':
|
||||||
if (editingTarget && Object.prototype.hasOwnProperty.call(editingTarget.variables, e.varId)) {
|
if (
|
||||||
|
editingTarget &&
|
||||||
|
Object.prototype.hasOwnProperty.call(
|
||||||
|
editingTarget.variables,
|
||||||
|
e.varId
|
||||||
|
)
|
||||||
|
) {
|
||||||
// This is a local variable, rename on the current target
|
// This is a local variable, rename on the current target
|
||||||
editingTarget.renameVariable(e.varId, e.newName);
|
editingTarget.renameVariable(e.varId, e.newName);
|
||||||
// Update all the blocks on the current target that use
|
// Update all the blocks on the current target that use
|
||||||
// this variable
|
// this variable
|
||||||
editingTarget.blocks.updateBlocksAfterVarRename(e.varId, e.newName);
|
editingTarget.blocks.updateBlocksAfterVarRename(
|
||||||
|
e.varId,
|
||||||
|
e.newName
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// This is a global variable
|
// This is a global variable
|
||||||
stage.renameVariable(e.varId, e.newName);
|
stage.renameVariable(e.varId, e.newName);
|
||||||
|
@ -410,14 +460,23 @@ class Blocks {
|
||||||
const targets = this.runtime.targets;
|
const targets = this.runtime.targets;
|
||||||
for (let i = 0; i < targets.length; i++) {
|
for (let i = 0; i < targets.length; i++) {
|
||||||
const currTarget = targets[i];
|
const currTarget = targets[i];
|
||||||
currTarget.blocks.updateBlocksAfterVarRename(e.varId, e.newName);
|
currTarget.blocks.updateBlocksAfterVarRename(
|
||||||
|
e.varId,
|
||||||
|
e.newName
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.emitProjectChanged();
|
this.emitProjectChanged();
|
||||||
break;
|
break;
|
||||||
case 'var_delete': {
|
case 'var_delete': {
|
||||||
const target = (editingTarget && Object.prototype.hasOwnProperty.call(editingTarget.variables, e.varId)) ?
|
const target =
|
||||||
editingTarget : stage;
|
editingTarget &&
|
||||||
|
Object.prototype.hasOwnProperty.call(
|
||||||
|
editingTarget.variables,
|
||||||
|
e.varId
|
||||||
|
) ?
|
||||||
|
editingTarget :
|
||||||
|
stage;
|
||||||
target.deleteVariable(e.varId);
|
target.deleteVariable(e.varId);
|
||||||
this.emitProjectChanged();
|
this.emitProjectChanged();
|
||||||
break;
|
break;
|
||||||
|
@ -425,11 +484,21 @@ class Blocks {
|
||||||
case 'comment_create':
|
case 'comment_create':
|
||||||
if (this.runtime.getEditingTarget()) {
|
if (this.runtime.getEditingTarget()) {
|
||||||
const currTarget = this.runtime.getEditingTarget();
|
const currTarget = this.runtime.getEditingTarget();
|
||||||
currTarget.createComment(e.commentId, e.blockId, e.text,
|
currTarget.createComment(
|
||||||
e.xy.x, e.xy.y, e.width, e.height, e.minimized);
|
e.commentId,
|
||||||
|
e.blockId,
|
||||||
|
e.text,
|
||||||
|
e.xy.x,
|
||||||
|
e.xy.y,
|
||||||
|
e.width,
|
||||||
|
e.height,
|
||||||
|
e.minimized
|
||||||
|
);
|
||||||
|
|
||||||
if (currTarget.comments[e.commentId].x === null &&
|
if (
|
||||||
currTarget.comments[e.commentId].y === null) {
|
currTarget.comments[e.commentId].x === null &&
|
||||||
|
currTarget.comments[e.commentId].y === null
|
||||||
|
) {
|
||||||
// Block comments imported from 2.0 projects are imported with their
|
// Block comments imported from 2.0 projects are imported with their
|
||||||
// x and y coordinates set to null so that scratch-blocks can
|
// x and y coordinates set to null so that scratch-blocks can
|
||||||
// auto-position them. If we are receiving a create event for these
|
// auto-position them. If we are receiving a create event for these
|
||||||
|
@ -445,8 +514,15 @@ class Blocks {
|
||||||
case 'comment_change':
|
case 'comment_change':
|
||||||
if (this.runtime.getEditingTarget()) {
|
if (this.runtime.getEditingTarget()) {
|
||||||
const currTarget = this.runtime.getEditingTarget();
|
const currTarget = this.runtime.getEditingTarget();
|
||||||
if (!Object.prototype.hasOwnProperty.call(currTarget.comments, e.commentId)) {
|
if (
|
||||||
log.warn(`Cannot change comment with id ${e.commentId} because it does not exist.`);
|
!Object.prototype.hasOwnProperty.call(
|
||||||
|
currTarget.comments,
|
||||||
|
e.commentId
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
log.warn(
|
||||||
|
`Cannot change comment with id ${e.commentId} because it does not exist.`
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const comment = currTarget.comments[e.commentId];
|
const comment = currTarget.comments[e.commentId];
|
||||||
|
@ -468,8 +544,16 @@ class Blocks {
|
||||||
case 'comment_move':
|
case 'comment_move':
|
||||||
if (this.runtime.getEditingTarget()) {
|
if (this.runtime.getEditingTarget()) {
|
||||||
const currTarget = this.runtime.getEditingTarget();
|
const currTarget = this.runtime.getEditingTarget();
|
||||||
if (currTarget && !Object.prototype.hasOwnProperty.call(currTarget.comments, e.commentId)) {
|
if (
|
||||||
log.warn(`Cannot change comment with id ${e.commentId} because it does not exist.`);
|
currTarget &&
|
||||||
|
!Object.prototype.hasOwnProperty.call(
|
||||||
|
currTarget.comments,
|
||||||
|
e.commentId
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
log.warn(
|
||||||
|
`Cannot move comment with id ${e.commentId} because it does not exist.`
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const comment = currTarget.comments[e.commentId];
|
const comment = currTarget.comments[e.commentId];
|
||||||
|
@ -483,7 +567,12 @@ class Blocks {
|
||||||
case 'comment_delete':
|
case 'comment_delete':
|
||||||
if (this.runtime.getEditingTarget()) {
|
if (this.runtime.getEditingTarget()) {
|
||||||
const currTarget = this.runtime.getEditingTarget();
|
const currTarget = this.runtime.getEditingTarget();
|
||||||
if (!Object.prototype.hasOwnProperty.call(currTarget.comments, e.commentId)) {
|
if (
|
||||||
|
!Object.prototype.hasOwnProperty.call(
|
||||||
|
currTarget.comments,
|
||||||
|
e.commentId
|
||||||
|
)
|
||||||
|
) {
|
||||||
// If we're in this state, we have probably received
|
// If we're in this state, we have probably received
|
||||||
// a delete event from a workspace that we switched from
|
// a delete event from a workspace that we switched from
|
||||||
// (e.g. a delete event for a comment on sprite a's workspace
|
// (e.g. a delete event for a comment on sprite a's workspace
|
||||||
|
@ -494,7 +583,9 @@ class Blocks {
|
||||||
if (e.blockId) {
|
if (e.blockId) {
|
||||||
const block = currTarget.blocks.getBlock(e.blockId);
|
const block = currTarget.blocks.getBlock(e.blockId);
|
||||||
if (!block) {
|
if (!block) {
|
||||||
log.warn(`Could not find block referenced by comment with id: ${e.commentId}`);
|
log.warn(
|
||||||
|
`Could not find block referenced by comment with id: ${e.commentId}`
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
delete block.comment;
|
delete block.comment;
|
||||||
|
@ -562,7 +653,9 @@ class Blocks {
|
||||||
*/
|
*/
|
||||||
changeBlock (args) {
|
changeBlock (args) {
|
||||||
// Validate
|
// Validate
|
||||||
if (['field', 'mutation', 'checkbox'].indexOf(args.element) === -1) return;
|
if (['field', 'mutation', 'checkbox'].indexOf(args.element) === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let block = this._blocks[args.id];
|
let block = this._blocks[args.id];
|
||||||
if (typeof block === 'undefined') return;
|
if (typeof block === 'undefined') return;
|
||||||
switch (args.element) {
|
switch (args.element) {
|
||||||
|
@ -576,13 +669,17 @@ class Blocks {
|
||||||
// 3. the checkbox should become unchecked if we're not already
|
// 3. the checkbox should become unchecked if we're not already
|
||||||
// monitoring current minute
|
// monitoring current minute
|
||||||
|
|
||||||
|
|
||||||
// Update block value
|
// Update block value
|
||||||
if (!block.fields[args.name]) return;
|
if (!block.fields[args.name]) return;
|
||||||
if (args.name === 'VARIABLE' || args.name === 'LIST' ||
|
if (
|
||||||
args.name === 'BROADCAST_OPTION') {
|
args.name === 'VARIABLE' ||
|
||||||
|
args.name === 'LIST' ||
|
||||||
|
args.name === 'BROADCAST_OPTION'
|
||||||
|
) {
|
||||||
// Get variable name using the id in args.value.
|
// Get variable name using the id in args.value.
|
||||||
const variable = this.runtime.getEditingTarget().lookupVariableById(args.value);
|
const variable = this.runtime
|
||||||
|
.getEditingTarget()
|
||||||
|
.lookupVariableById(args.value);
|
||||||
if (variable) {
|
if (variable) {
|
||||||
block.fields[args.name].value = variable.name;
|
block.fields[args.name].value = variable.name;
|
||||||
block.fields[args.name].id = args.value;
|
block.fields[args.name].id = args.value;
|
||||||
|
@ -596,19 +693,26 @@ class Blocks {
|
||||||
// TODO: (#1787)
|
// TODO: (#1787)
|
||||||
if (block.opcode === 'sensing_of_object_menu') {
|
if (block.opcode === 'sensing_of_object_menu') {
|
||||||
if (block.fields.OBJECT.value === '_stage_') {
|
if (block.fields.OBJECT.value === '_stage_') {
|
||||||
this._blocks[block.parent].fields.PROPERTY.value = 'backdrop #';
|
this._blocks[block.parent].fields.PROPERTY.value =
|
||||||
|
'backdrop #';
|
||||||
} else {
|
} else {
|
||||||
this._blocks[block.parent].fields.PROPERTY.value = 'x position';
|
this._blocks[block.parent].fields.PROPERTY.value =
|
||||||
|
'x position';
|
||||||
}
|
}
|
||||||
this.runtime.requestBlocksUpdate();
|
this.runtime.requestBlocksUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
const flyoutBlock = block.shadow && block.parent ? this._blocks[block.parent] : block;
|
const flyoutBlock =
|
||||||
|
block.shadow && block.parent ?
|
||||||
|
this._blocks[block.parent] :
|
||||||
|
block;
|
||||||
if (flyoutBlock.isMonitored) {
|
if (flyoutBlock.isMonitored) {
|
||||||
this.runtime.requestUpdateMonitor(Map({
|
this.runtime.requestUpdateMonitor(
|
||||||
|
Map({
|
||||||
id: flyoutBlock.id,
|
id: flyoutBlock.id,
|
||||||
params: this._getBlockParams(flyoutBlock)
|
params: this._getBlockParams(flyoutBlock)
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -619,14 +723,20 @@ class Blocks {
|
||||||
// A checkbox usually has a one to one correspondence with the monitor
|
// A checkbox usually has a one to one correspondence with the monitor
|
||||||
// block but in the case of monitored reporters that have arguments,
|
// block but in the case of monitored reporters that have arguments,
|
||||||
// map the old id to a new id, creating a new monitor block if necessary
|
// map the old id to a new id, creating a new monitor block if necessary
|
||||||
if (block.fields && Object.keys(block.fields).length > 0 &&
|
if (
|
||||||
block.opcode !== 'data_variable' && block.opcode !== 'data_listcontents') {
|
block.fields &&
|
||||||
|
Object.keys(block.fields).length > 0 &&
|
||||||
|
block.opcode !== 'data_variable' &&
|
||||||
|
block.opcode !== 'data_listcontents'
|
||||||
|
) {
|
||||||
// This block has an argument which needs to get separated out into
|
// This block has an argument which needs to get separated out into
|
||||||
// multiple monitor blocks with ids based on the selected argument
|
// multiple monitor blocks with ids based on the selected argument
|
||||||
const newId = getMonitorIdForBlockWithArgs(block.id, block.fields);
|
|
||||||
// Note: we're not just constantly creating a longer and longer id everytime we check
|
// Note: we're not just constantly creating a longer and longer id everytime we check
|
||||||
// the checkbox because we're using the id of the block in the flyout as the base
|
// the checkbox because we're using the id of the block in the flyout as the base
|
||||||
|
const newId = getMonitorIdForBlockWithArgs(
|
||||||
|
block.id,
|
||||||
|
block.fields
|
||||||
|
);
|
||||||
|
|
||||||
// check if a block with the new id already exists, otherwise create
|
// check if a block with the new id already exists, otherwise create
|
||||||
let newBlock = this.runtime.monitorBlocks.getBlock(newId);
|
let newBlock = this.runtime.monitorBlocks.getBlock(newId);
|
||||||
|
@ -645,19 +755,31 @@ class Blocks {
|
||||||
// Variable blocks may be sprite specific depending on the owner of the variable
|
// Variable blocks may be sprite specific depending on the owner of the variable
|
||||||
let isSpriteLocalVariable = false;
|
let isSpriteLocalVariable = false;
|
||||||
if (block.opcode === 'data_variable') {
|
if (block.opcode === 'data_variable') {
|
||||||
isSpriteLocalVariable = !(this.runtime.getTargetForStage().variables[block.fields.VARIABLE.id]);
|
isSpriteLocalVariable =
|
||||||
|
!this.runtime.getTargetForStage().variables[
|
||||||
|
block.fields.VARIABLE.id
|
||||||
|
];
|
||||||
} else if (block.opcode === 'data_listcontents') {
|
} else if (block.opcode === 'data_listcontents') {
|
||||||
isSpriteLocalVariable = !(this.runtime.getTargetForStage().variables[block.fields.LIST.id]);
|
isSpriteLocalVariable =
|
||||||
|
!this.runtime.getTargetForStage().variables[
|
||||||
|
block.fields.LIST.id
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSpriteSpecific = isSpriteLocalVariable ||
|
const isSpriteSpecific =
|
||||||
(Object.prototype.hasOwnProperty.call(this.runtime.monitorBlockInfo, block.opcode) &&
|
isSpriteLocalVariable ||
|
||||||
this.runtime.monitorBlockInfo[block.opcode].isSpriteSpecific);
|
(Object.prototype.hasOwnProperty.call(
|
||||||
|
this.runtime.monitorBlockInfo,
|
||||||
|
block.opcode
|
||||||
|
) &&
|
||||||
|
this.runtime.monitorBlockInfo[block.opcode]
|
||||||
|
.isSpriteSpecific);
|
||||||
if (isSpriteSpecific) {
|
if (isSpriteSpecific) {
|
||||||
// If creating a new sprite specific monitor, the only possible target is
|
// If creating a new sprite specific monitor, the only possible target is
|
||||||
// the current editing one b/c you cannot dynamically create monitors.
|
// the current editing one b/c you cannot dynamically create monitors.
|
||||||
// Also, do not change the targetId if it has already been assigned
|
// Also, do not change the targetId if it has already been assigned
|
||||||
block.targetId = block.targetId || this.runtime.getEditingTarget().id;
|
block.targetId =
|
||||||
|
block.targetId || this.runtime.getEditingTarget().id;
|
||||||
} else {
|
} else {
|
||||||
block.targetId = null;
|
block.targetId = null;
|
||||||
}
|
}
|
||||||
|
@ -667,16 +789,25 @@ class Blocks {
|
||||||
} else if (!wasMonitored && block.isMonitored) {
|
} else if (!wasMonitored && block.isMonitored) {
|
||||||
// Tries to show the monitor for specified block. If it doesn't exist, add the monitor.
|
// Tries to show the monitor for specified block. If it doesn't exist, add the monitor.
|
||||||
if (!this.runtime.requestShowMonitor(block.id)) {
|
if (!this.runtime.requestShowMonitor(block.id)) {
|
||||||
this.runtime.requestAddMonitor(MonitorRecord({
|
this.runtime.requestAddMonitor(
|
||||||
|
MonitorRecord({
|
||||||
id: block.id,
|
id: block.id,
|
||||||
targetId: block.targetId,
|
targetId: block.targetId,
|
||||||
spriteName: block.targetId ? this.runtime.getTargetById(block.targetId).getName() : null,
|
spriteName: block.targetId ?
|
||||||
|
this.runtime
|
||||||
|
.getTargetById(block.targetId)
|
||||||
|
.getName() :
|
||||||
|
null,
|
||||||
opcode: block.opcode,
|
opcode: block.opcode,
|
||||||
params: this._getBlockParams(block),
|
params: this._getBlockParams(block),
|
||||||
// @todo(vm#565) for numerical values with decimals, some countries use comma
|
// @todo(vm#565) for numerical values with decimals, some countries use comma
|
||||||
value: '',
|
value: '',
|
||||||
mode: block.opcode === 'data_listcontents' ? 'list' : 'default'
|
mode:
|
||||||
}));
|
block.opcode === 'data_listcontents' ?
|
||||||
|
'list' :
|
||||||
|
'default'
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -705,8 +836,8 @@ class Blocks {
|
||||||
|
|
||||||
// Move coordinate changes.
|
// Move coordinate changes.
|
||||||
if (e.newCoordinate) {
|
if (e.newCoordinate) {
|
||||||
|
didChange =
|
||||||
didChange = (block.x !== e.newCoordinate.x) || (block.y !== e.newCoordinate.y);
|
block.x !== e.newCoordinate.x || block.y !== e.newCoordinate.y;
|
||||||
|
|
||||||
block.x = e.newCoordinate.x;
|
block.x = e.newCoordinate.x;
|
||||||
block.y = e.newCoordinate.y;
|
block.y = e.newCoordinate.y;
|
||||||
|
@ -715,8 +846,10 @@ class Blocks {
|
||||||
// Remove from any old parent.
|
// Remove from any old parent.
|
||||||
if (typeof e.oldParent !== 'undefined') {
|
if (typeof e.oldParent !== 'undefined') {
|
||||||
const oldParent = this._blocks[e.oldParent];
|
const oldParent = this._blocks[e.oldParent];
|
||||||
if (typeof e.oldInput !== 'undefined' &&
|
if (
|
||||||
oldParent.inputs[e.oldInput].block === e.id) {
|
typeof e.oldInput !== 'undefined' &&
|
||||||
|
oldParent.inputs[e.oldInput].block === e.id
|
||||||
|
) {
|
||||||
// This block was connected to the old parent's input.
|
// This block was connected to the old parent's input.
|
||||||
oldParent.inputs[e.oldInput].block = null;
|
oldParent.inputs[e.oldInput].block = null;
|
||||||
} else if (oldParent.next === e.id) {
|
} else if (oldParent.next === e.id) {
|
||||||
|
@ -741,8 +874,14 @@ class Blocks {
|
||||||
// Moved to the new parent's input.
|
// Moved to the new parent's input.
|
||||||
// Don't obscure the shadow block.
|
// Don't obscure the shadow block.
|
||||||
let oldShadow = null;
|
let oldShadow = null;
|
||||||
if (Object.prototype.hasOwnProperty.call(this._blocks[e.newParent].inputs, e.newInput)) {
|
if (
|
||||||
oldShadow = this._blocks[e.newParent].inputs[e.newInput].shadow;
|
Object.prototype.hasOwnProperty.call(
|
||||||
|
this._blocks[e.newParent].inputs,
|
||||||
|
e.newInput
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
oldShadow =
|
||||||
|
this._blocks[e.newParent].inputs[e.newInput].shadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the block being attached is itself a shadow, make sure to set
|
// If the block being attached is itself a shadow, make sure to set
|
||||||
|
@ -764,7 +903,6 @@ class Blocks {
|
||||||
if (didChange) this.emitProjectChanged();
|
if (didChange) this.emitProjectChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block management: run all blocks.
|
* Block management: run all blocks.
|
||||||
* @param {!object} runtime Runtime to run all blocks in.
|
* @param {!object} runtime Runtime to run all blocks in.
|
||||||
|
@ -777,7 +915,9 @@ class Blocks {
|
||||||
const targetId = this.getBlock(blockId).targetId;
|
const targetId = this.getBlock(blockId).targetId;
|
||||||
return {
|
return {
|
||||||
blockId,
|
blockId,
|
||||||
target: targetId ? runtime.getTargetById(targetId) : null
|
target: targetId ?
|
||||||
|
runtime.getTargetById(targetId) :
|
||||||
|
null
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -816,8 +956,10 @@ class Blocks {
|
||||||
this.deleteBlock(block.inputs[input].block);
|
this.deleteBlock(block.inputs[input].block);
|
||||||
}
|
}
|
||||||
// Delete obscured shadow blocks.
|
// Delete obscured shadow blocks.
|
||||||
if (block.inputs[input].shadow !== null &&
|
if (
|
||||||
block.inputs[input].shadow !== block.inputs[input].block) {
|
block.inputs[input].shadow !== null &&
|
||||||
|
block.inputs[input].shadow !== block.inputs[input].block
|
||||||
|
) {
|
||||||
this.deleteBlock(block.inputs[input].shadow);
|
this.deleteBlock(block.inputs[input].shadow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -863,7 +1005,10 @@ class Blocks {
|
||||||
} else if (blocks[blockId].fields.LIST) {
|
} else if (blocks[blockId].fields.LIST) {
|
||||||
varOrListField = blocks[blockId].fields.LIST;
|
varOrListField = blocks[blockId].fields.LIST;
|
||||||
varType = Variable.LIST_TYPE;
|
varType = Variable.LIST_TYPE;
|
||||||
} else if (optIncludeBroadcast && blocks[blockId].fields.BROADCAST_OPTION) {
|
} else if (
|
||||||
|
optIncludeBroadcast &&
|
||||||
|
blocks[blockId].fields.BROADCAST_OPTION
|
||||||
|
) {
|
||||||
varOrListField = blocks[blockId].fields.BROADCAST_OPTION;
|
varOrListField = blocks[blockId].fields.BROADCAST_OPTION;
|
||||||
varType = Variable.BROADCAST_MESSAGE_TYPE;
|
varType = Variable.BROADCAST_MESSAGE_TYPE;
|
||||||
}
|
}
|
||||||
|
@ -875,10 +1020,12 @@ class Blocks {
|
||||||
type: varType
|
type: varType
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
allReferences[currVarId] = [{
|
allReferences[currVarId] = [
|
||||||
|
{
|
||||||
referencingField: varOrListField,
|
referencingField: varOrListField,
|
||||||
type: varType
|
type: varType
|
||||||
}];
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -915,9 +1062,15 @@ class Blocks {
|
||||||
updateTargetSpecificBlocks (isStage) {
|
updateTargetSpecificBlocks (isStage) {
|
||||||
const blocks = this._blocks;
|
const blocks = this._blocks;
|
||||||
for (const blockId in blocks) {
|
for (const blockId in blocks) {
|
||||||
if (isStage && blocks[blockId].opcode === 'event_whenthisspriteclicked') {
|
if (
|
||||||
|
isStage &&
|
||||||
|
blocks[blockId].opcode === 'event_whenthisspriteclicked'
|
||||||
|
) {
|
||||||
blocks[blockId].opcode = 'event_whenstageclicked';
|
blocks[blockId].opcode = 'event_whenstageclicked';
|
||||||
} else if (!isStage && blocks[blockId].opcode === 'event_whenstageclicked') {
|
} else if (
|
||||||
|
!isStage &&
|
||||||
|
blocks[blockId].opcode === 'event_whenstageclicked'
|
||||||
|
) {
|
||||||
blocks[blockId].opcode = 'event_whenthisspriteclicked';
|
blocks[blockId].opcode = 'event_whenthisspriteclicked';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -967,10 +1120,12 @@ class Blocks {
|
||||||
let blockUpdated = false;
|
let blockUpdated = false;
|
||||||
for (const blockId in blocks) {
|
for (const blockId in blocks) {
|
||||||
const block = blocks[blockId];
|
const block = blocks[blockId];
|
||||||
if (block.opcode === 'sensing_of' &&
|
if (
|
||||||
|
block.opcode === 'sensing_of' &&
|
||||||
block.fields.PROPERTY.value === oldName &&
|
block.fields.PROPERTY.value === oldName &&
|
||||||
// If block and shadow are different, it means a block is inserted to OBJECT, and should be ignored.
|
// If block and shadow are different, it means a block is inserted to OBJECT, and should be ignored.
|
||||||
block.inputs.OBJECT.block === block.inputs.OBJECT.shadow) {
|
block.inputs.OBJECT.block === block.inputs.OBJECT.shadow
|
||||||
|
) {
|
||||||
const inputBlock = this.getBlock(block.inputs.OBJECT.block);
|
const inputBlock = this.getBlock(block.inputs.OBJECT.block);
|
||||||
if (inputBlock.fields.OBJECT.value === targetName) {
|
if (inputBlock.fields.OBJECT.value === targetName) {
|
||||||
block.fields.PROPERTY.value = newName;
|
block.fields.PROPERTY.value = newName;
|
||||||
|
@ -991,7 +1146,10 @@ class Blocks {
|
||||||
*/
|
*/
|
||||||
_getCostumeField (blockId) {
|
_getCostumeField (blockId) {
|
||||||
const block = this.getBlock(blockId);
|
const block = this.getBlock(blockId);
|
||||||
if (block && Object.prototype.hasOwnProperty.call(block.fields, 'COSTUME')) {
|
if (
|
||||||
|
block &&
|
||||||
|
Object.prototype.hasOwnProperty.call(block.fields, 'COSTUME')
|
||||||
|
) {
|
||||||
return block.fields.COSTUME;
|
return block.fields.COSTUME;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -1006,7 +1164,10 @@ class Blocks {
|
||||||
*/
|
*/
|
||||||
_getSoundField (blockId) {
|
_getSoundField (blockId) {
|
||||||
const block = this.getBlock(blockId);
|
const block = this.getBlock(blockId);
|
||||||
if (block && Object.prototype.hasOwnProperty.call(block.fields, 'SOUND_MENU')) {
|
if (
|
||||||
|
block &&
|
||||||
|
Object.prototype.hasOwnProperty.call(block.fields, 'SOUND_MENU')
|
||||||
|
) {
|
||||||
return block.fields.SOUND_MENU;
|
return block.fields.SOUND_MENU;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -1021,7 +1182,10 @@ class Blocks {
|
||||||
*/
|
*/
|
||||||
_getBackdropField (blockId) {
|
_getBackdropField (blockId) {
|
||||||
const block = this.getBlock(blockId);
|
const block = this.getBlock(blockId);
|
||||||
if (block && Object.prototype.hasOwnProperty.call(block.fields, 'BACKDROP')) {
|
if (
|
||||||
|
block &&
|
||||||
|
Object.prototype.hasOwnProperty.call(block.fields, 'BACKDROP')
|
||||||
|
) {
|
||||||
return block.fields.BACKDROP;
|
return block.fields.BACKDROP;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -1039,8 +1203,15 @@ class Blocks {
|
||||||
if (!block) {
|
if (!block) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const spriteMenuNames = ['TOWARDS', 'TO', 'OBJECT', 'VIDEOONMENU2',
|
const spriteMenuNames = [
|
||||||
'DISTANCETOMENU', 'TOUCHINGOBJECTMENU', 'CLONE_OPTION'];
|
'TOWARDS',
|
||||||
|
'TO',
|
||||||
|
'OBJECT',
|
||||||
|
'VIDEOONMENU2',
|
||||||
|
'DISTANCETOMENU',
|
||||||
|
'TOUCHINGOBJECTMENU',
|
||||||
|
'CLONE_OPTION'
|
||||||
|
];
|
||||||
for (let i = 0; i < spriteMenuNames.length; i++) {
|
for (let i = 0; i < spriteMenuNames.length; i++) {
|
||||||
const menuName = spriteMenuNames[i];
|
const menuName = spriteMenuNames[i];
|
||||||
if (Object.prototype.hasOwnProperty.call(block.fields, menuName)) {
|
if (Object.prototype.hasOwnProperty.call(block.fields, menuName)) {
|
||||||
|
@ -1059,7 +1230,9 @@ class Blocks {
|
||||||
* @return {string} String of XML representing this object's blocks.
|
* @return {string} String of XML representing this object's blocks.
|
||||||
*/
|
*/
|
||||||
toXML (comments) {
|
toXML (comments) {
|
||||||
return this._scripts.map(script => this.blockToXML(script, comments)).join();
|
return this._scripts
|
||||||
|
.map(script => this.blockToXML(script, comments))
|
||||||
|
.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1076,9 +1249,8 @@ class Blocks {
|
||||||
// this early exit allows the project to load.
|
// this early exit allows the project to load.
|
||||||
if (!block) return;
|
if (!block) return;
|
||||||
// Encode properties of this block.
|
// Encode properties of this block.
|
||||||
const tagName = (block.shadow) ? 'shadow' : 'block';
|
const tagName = block.shadow ? 'shadow' : 'block';
|
||||||
let xmlString =
|
let xmlString = `<${tagName}
|
||||||
`<${tagName}
|
|
||||||
id="${block.id}"
|
id="${block.id}"
|
||||||
type="${block.opcode}"
|
type="${block.opcode}"
|
||||||
${block.topLevel ? `x="${block.x}" y="${block.y}"` : ''}
|
${block.topLevel ? `x="${block.x}" y="${block.y}"` : ''}
|
||||||
|
@ -1089,10 +1261,14 @@ class Blocks {
|
||||||
if (Object.prototype.hasOwnProperty.call(comments, commentId)) {
|
if (Object.prototype.hasOwnProperty.call(comments, commentId)) {
|
||||||
xmlString += comments[commentId].toXML();
|
xmlString += comments[commentId].toXML();
|
||||||
} else {
|
} else {
|
||||||
log.warn(`Could not find comment with id: ${commentId} in provided comment descriptions.`);
|
log.warn(
|
||||||
|
`Could not find comment with id: ${commentId} in provided comment descriptions.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.warn(`Cannot serialize comment with id: ${commentId}; no comment descriptions provided.`);
|
log.warn(
|
||||||
|
`Cannot serialize comment with id: ${commentId}; no comment descriptions provided.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add any mutation. Must come before inputs.
|
// Add any mutation. Must come before inputs.
|
||||||
|
@ -1101,7 +1277,9 @@ class Blocks {
|
||||||
}
|
}
|
||||||
// Add any inputs on this block.
|
// Add any inputs on this block.
|
||||||
for (const input in block.inputs) {
|
for (const input in block.inputs) {
|
||||||
if (!Object.prototype.hasOwnProperty.call(block.inputs, input)) continue;
|
if (!Object.prototype.hasOwnProperty.call(block.inputs, input)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const blockInput = block.inputs[input];
|
const blockInput = block.inputs[input];
|
||||||
// Only encode a value tag if the value input is occupied.
|
// Only encode a value tag if the value input is occupied.
|
||||||
if (blockInput.block || blockInput.shadow) {
|
if (blockInput.block || blockInput.shadow) {
|
||||||
|
@ -1109,7 +1287,10 @@ class Blocks {
|
||||||
if (blockInput.block) {
|
if (blockInput.block) {
|
||||||
xmlString += this.blockToXML(blockInput.block, comments);
|
xmlString += this.blockToXML(blockInput.block, comments);
|
||||||
}
|
}
|
||||||
if (blockInput.shadow && blockInput.shadow !== blockInput.block) {
|
if (
|
||||||
|
blockInput.shadow &&
|
||||||
|
blockInput.shadow !== blockInput.block
|
||||||
|
) {
|
||||||
// Obscured shadow.
|
// Obscured shadow.
|
||||||
xmlString += this.blockToXML(blockInput.shadow, comments);
|
xmlString += this.blockToXML(blockInput.shadow, comments);
|
||||||
}
|
}
|
||||||
|
@ -1118,7 +1299,9 @@ class Blocks {
|
||||||
}
|
}
|
||||||
// Add any fields on this block.
|
// Add any fields on this block.
|
||||||
for (const field in block.fields) {
|
for (const field in block.fields) {
|
||||||
if (!Object.prototype.hasOwnProperty.call(block.fields, field)) continue;
|
if (!Object.prototype.hasOwnProperty.call(block.fields, field)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const blockField = block.fields[field];
|
const blockField = block.fields[field];
|
||||||
xmlString += `<field name="${blockField.name}"`;
|
xmlString += `<field name="${blockField.name}"`;
|
||||||
const fieldId = blockField.id;
|
const fieldId = blockField.id;
|
||||||
|
@ -1137,7 +1320,10 @@ class Blocks {
|
||||||
}
|
}
|
||||||
// Add blocks connected to the next connection.
|
// Add blocks connected to the next connection.
|
||||||
if (block.next) {
|
if (block.next) {
|
||||||
xmlString += `<next>${this.blockToXML(block.next, comments)}</next>`;
|
xmlString += `<next>${this.blockToXML(
|
||||||
|
block.next,
|
||||||
|
comments
|
||||||
|
)}</next>`;
|
||||||
}
|
}
|
||||||
xmlString += `</${tagName}>`;
|
xmlString += `</${tagName}>`;
|
||||||
return xmlString;
|
return xmlString;
|
||||||
|
@ -1152,8 +1338,10 @@ class Blocks {
|
||||||
let mutationString = `<${mutation.tagName}`;
|
let mutationString = `<${mutation.tagName}`;
|
||||||
for (const prop in mutation) {
|
for (const prop in mutation) {
|
||||||
if (prop === 'children' || prop === 'tagName') continue;
|
if (prop === 'children' || prop === 'tagName') continue;
|
||||||
let mutationValue = (typeof mutation[prop] === 'string') ?
|
let mutationValue =
|
||||||
xmlEscape(mutation[prop]) : mutation[prop];
|
typeof mutation[prop] === 'string' ?
|
||||||
|
xmlEscape(mutation[prop]) :
|
||||||
|
mutation[prop];
|
||||||
|
|
||||||
// Handle dynamic extension blocks
|
// Handle dynamic extension blocks
|
||||||
if (prop === 'blockInfo') {
|
if (prop === 'blockInfo') {
|
||||||
|
|
|
@ -385,7 +385,8 @@ class Runtime extends EventEmitter {
|
||||||
* being added.
|
* being added.
|
||||||
* @type {function}
|
* @type {function}
|
||||||
*/
|
*/
|
||||||
this.addCloudVariable = this._initializeAddCloudVariable(newCloudDataManager);
|
this.addCloudVariable =
|
||||||
|
this._initializeAddCloudVariable(newCloudDataManager);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function which updates the runtime's cloud variable limit
|
* A function which updates the runtime's cloud variable limit
|
||||||
|
@ -393,7 +394,8 @@ class Runtime extends EventEmitter {
|
||||||
* if the last of the cloud variables is being removed.
|
* if the last of the cloud variables is being removed.
|
||||||
* @type {function}
|
* @type {function}
|
||||||
*/
|
*/
|
||||||
this.removeCloudVariable = this._initializeRemoveCloudVariable(newCloudDataManager);
|
this.removeCloudVariable =
|
||||||
|
this._initializeRemoveCloudVariable(newCloudDataManager);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A string representing the origin of the current project from outside of the
|
* A string representing the origin of the current project from outside of the
|
||||||
|
@ -737,24 +739,24 @@ class Runtime extends EventEmitter {
|
||||||
// Helper function for initializing the addCloudVariable function
|
// Helper function for initializing the addCloudVariable function
|
||||||
_initializeAddCloudVariable (newCloudDataManager) {
|
_initializeAddCloudVariable (newCloudDataManager) {
|
||||||
// The addCloudVariable function
|
// The addCloudVariable function
|
||||||
return (() => {
|
return () => {
|
||||||
const hadCloudVarsBefore = this.hasCloudData();
|
const hadCloudVarsBefore = this.hasCloudData();
|
||||||
newCloudDataManager.addCloudVariable();
|
newCloudDataManager.addCloudVariable();
|
||||||
if (!hadCloudVarsBefore && this.hasCloudData()) {
|
if (!hadCloudVarsBefore && this.hasCloudData()) {
|
||||||
this.emit(Runtime.HAS_CLOUD_DATA_UPDATE, true);
|
this.emit(Runtime.HAS_CLOUD_DATA_UPDATE, true);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function for initializing the removeCloudVariable function
|
// Helper function for initializing the removeCloudVariable function
|
||||||
_initializeRemoveCloudVariable (newCloudDataManager) {
|
_initializeRemoveCloudVariable (newCloudDataManager) {
|
||||||
return (() => {
|
return () => {
|
||||||
const hadCloudVarsBefore = this.hasCloudData();
|
const hadCloudVarsBefore = this.hasCloudData();
|
||||||
newCloudDataManager.removeCloudVariable();
|
newCloudDataManager.removeCloudVariable();
|
||||||
if (hadCloudVarsBefore && !this.hasCloudData()) {
|
if (hadCloudVarsBefore && !this.hasCloudData()) {
|
||||||
this.emit(Runtime.HAS_CLOUD_DATA_UPDATE, false);
|
this.emit(Runtime.HAS_CLOUD_DATA_UPDATE, false);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -764,14 +766,26 @@ class Runtime extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
_registerBlockPackages () {
|
_registerBlockPackages () {
|
||||||
for (const packageName in defaultBlockPackages) {
|
for (const packageName in defaultBlockPackages) {
|
||||||
if (Object.prototype.hasOwnProperty.call(defaultBlockPackages, packageName)) {
|
if (
|
||||||
|
Object.prototype.hasOwnProperty.call(
|
||||||
|
defaultBlockPackages,
|
||||||
|
packageName
|
||||||
|
)
|
||||||
|
) {
|
||||||
// @todo pass a different runtime depending on package privilege?
|
// @todo pass a different runtime depending on package privilege?
|
||||||
const packageObject = new (defaultBlockPackages[packageName])(this);
|
const packageObject = new defaultBlockPackages[packageName](
|
||||||
|
this
|
||||||
|
);
|
||||||
// Collect primitives from package.
|
// Collect primitives from package.
|
||||||
if (packageObject.getPrimitives) {
|
if (packageObject.getPrimitives) {
|
||||||
const packagePrimitives = packageObject.getPrimitives();
|
const packagePrimitives = packageObject.getPrimitives();
|
||||||
for (const op in packagePrimitives) {
|
for (const op in packagePrimitives) {
|
||||||
if (Object.prototype.hasOwnProperty.call(packagePrimitives, op)) {
|
if (
|
||||||
|
Object.prototype.hasOwnProperty.call(
|
||||||
|
packagePrimitives,
|
||||||
|
op
|
||||||
|
)
|
||||||
|
) {
|
||||||
this._primitives[op] =
|
this._primitives[op] =
|
||||||
packagePrimitives[op].bind(packageObject);
|
packagePrimitives[op].bind(packageObject);
|
||||||
}
|
}
|
||||||
|
@ -781,14 +795,23 @@ class Runtime extends EventEmitter {
|
||||||
if (packageObject.getHats) {
|
if (packageObject.getHats) {
|
||||||
const packageHats = packageObject.getHats();
|
const packageHats = packageObject.getHats();
|
||||||
for (const hatName in packageHats) {
|
for (const hatName in packageHats) {
|
||||||
if (Object.prototype.hasOwnProperty.call(packageHats, hatName)) {
|
if (
|
||||||
|
Object.prototype.hasOwnProperty.call(
|
||||||
|
packageHats,
|
||||||
|
hatName
|
||||||
|
)
|
||||||
|
) {
|
||||||
this._hats[hatName] = packageHats[hatName];
|
this._hats[hatName] = packageHats[hatName];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Collect monitored from package.
|
// Collect monitored from package.
|
||||||
if (packageObject.getMonitored) {
|
if (packageObject.getMonitored) {
|
||||||
this.monitorBlockInfo = Object.assign({}, this.monitorBlockInfo, packageObject.getMonitored());
|
this.monitorBlockInfo = Object.assign(
|
||||||
|
{},
|
||||||
|
this.monitorBlockInfo,
|
||||||
|
packageObject.getMonitored()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -818,7 +841,9 @@ class Runtime extends EventEmitter {
|
||||||
const context = {};
|
const context = {};
|
||||||
target = target || this.getEditingTarget() || this.getTargetForStage();
|
target = target || this.getEditingTarget() || this.getTargetForStage();
|
||||||
if (target) {
|
if (target) {
|
||||||
context.targetType = (target.isStage ? TargetType.STAGE : TargetType.SPRITE);
|
context.targetType = target.isStage ?
|
||||||
|
TargetType.STAGE :
|
||||||
|
TargetType.SPRITE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -851,8 +876,14 @@ class Runtime extends EventEmitter {
|
||||||
this._fillExtensionCategory(categoryInfo, extensionInfo);
|
this._fillExtensionCategory(categoryInfo, extensionInfo);
|
||||||
|
|
||||||
for (const fieldTypeName in categoryInfo.customFieldTypes) {
|
for (const fieldTypeName in categoryInfo.customFieldTypes) {
|
||||||
if (Object.prototype.hasOwnProperty.call(extensionInfo.customFieldTypes, fieldTypeName)) {
|
if (
|
||||||
const fieldTypeInfo = categoryInfo.customFieldTypes[fieldTypeName];
|
Object.prototype.hasOwnProperty.call(
|
||||||
|
extensionInfo.customFieldTypes,
|
||||||
|
fieldTypeName
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const fieldTypeInfo =
|
||||||
|
categoryInfo.customFieldTypes[fieldTypeName];
|
||||||
|
|
||||||
// Emit events for custom field types from extension
|
// Emit events for custom field types from extension
|
||||||
this.emit(Runtime.EXTENSION_FIELD_ADDED, {
|
this.emit(Runtime.EXTENSION_FIELD_ADDED, {
|
||||||
|
@ -871,7 +902,9 @@ class Runtime extends EventEmitter {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_refreshExtensionPrimitives (extensionInfo) {
|
_refreshExtensionPrimitives (extensionInfo) {
|
||||||
const categoryInfo = this._blockInfo.find(info => info.id === extensionInfo.id);
|
const categoryInfo = this._blockInfo.find(
|
||||||
|
info => info.id === extensionInfo.id
|
||||||
|
);
|
||||||
if (categoryInfo) {
|
if (categoryInfo) {
|
||||||
categoryInfo.name = maybeFormatMessage(extensionInfo.name);
|
categoryInfo.name = maybeFormatMessage(extensionInfo.name);
|
||||||
this._fillExtensionCategory(categoryInfo, extensionInfo);
|
this._fillExtensionCategory(categoryInfo, extensionInfo);
|
||||||
|
@ -894,15 +927,29 @@ class Runtime extends EventEmitter {
|
||||||
categoryInfo.menuInfo = {};
|
categoryInfo.menuInfo = {};
|
||||||
|
|
||||||
for (const menuName in extensionInfo.menus) {
|
for (const menuName in extensionInfo.menus) {
|
||||||
if (Object.prototype.hasOwnProperty.call(extensionInfo.menus, menuName)) {
|
if (
|
||||||
|
Object.prototype.hasOwnProperty.call(
|
||||||
|
extensionInfo.menus,
|
||||||
|
menuName
|
||||||
|
)
|
||||||
|
) {
|
||||||
const menuInfo = extensionInfo.menus[menuName];
|
const menuInfo = extensionInfo.menus[menuName];
|
||||||
const convertedMenu = this._buildMenuForScratchBlocks(menuName, menuInfo, categoryInfo);
|
const convertedMenu = this._buildMenuForScratchBlocks(
|
||||||
|
menuName,
|
||||||
|
menuInfo,
|
||||||
|
categoryInfo
|
||||||
|
);
|
||||||
categoryInfo.menus.push(convertedMenu);
|
categoryInfo.menus.push(convertedMenu);
|
||||||
categoryInfo.menuInfo[menuName] = menuInfo;
|
categoryInfo.menuInfo[menuName] = menuInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const fieldTypeName in extensionInfo.customFieldTypes) {
|
for (const fieldTypeName in extensionInfo.customFieldTypes) {
|
||||||
if (Object.prototype.hasOwnProperty.call(extensionInfo.customFieldTypes, fieldTypeName)) {
|
if (
|
||||||
|
Object.prototype.hasOwnProperty.call(
|
||||||
|
extensionInfo.customFieldTypes,
|
||||||
|
fieldTypeName
|
||||||
|
)
|
||||||
|
) {
|
||||||
const fieldType = extensionInfo.customFieldTypes[fieldTypeName];
|
const fieldType = extensionInfo.customFieldTypes[fieldTypeName];
|
||||||
const fieldTypeInfo = this._buildCustomFieldInfo(
|
const fieldTypeInfo = this._buildCustomFieldInfo(
|
||||||
fieldTypeName,
|
fieldTypeName,
|
||||||
|
@ -917,22 +964,32 @@ class Runtime extends EventEmitter {
|
||||||
|
|
||||||
for (const blockInfo of extensionInfo.blocks) {
|
for (const blockInfo of extensionInfo.blocks) {
|
||||||
try {
|
try {
|
||||||
const convertedBlock = this._convertForScratchBlocks(blockInfo, categoryInfo);
|
const convertedBlock = this._convertForScratchBlocks(
|
||||||
|
blockInfo,
|
||||||
|
categoryInfo
|
||||||
|
);
|
||||||
categoryInfo.blocks.push(convertedBlock);
|
categoryInfo.blocks.push(convertedBlock);
|
||||||
if (convertedBlock.json) {
|
if (convertedBlock.json) {
|
||||||
const opcode = convertedBlock.json.type;
|
const opcode = convertedBlock.json.type;
|
||||||
if (blockInfo.blockType !== BlockType.EVENT) {
|
if (blockInfo.blockType !== BlockType.EVENT) {
|
||||||
this._primitives[opcode] = convertedBlock.info.func;
|
this._primitives[opcode] = convertedBlock.info.func;
|
||||||
}
|
}
|
||||||
if (blockInfo.blockType === BlockType.EVENT || blockInfo.blockType === BlockType.HAT) {
|
if (
|
||||||
|
blockInfo.blockType === BlockType.EVENT ||
|
||||||
|
blockInfo.blockType === BlockType.HAT
|
||||||
|
) {
|
||||||
this._hats[opcode] = {
|
this._hats[opcode] = {
|
||||||
edgeActivated: blockInfo.isEdgeActivated,
|
edgeActivated: blockInfo.isEdgeActivated,
|
||||||
restartExistingThreads: blockInfo.shouldRestartExistingThreads
|
restartExistingThreads:
|
||||||
|
blockInfo.shouldRestartExistingThreads
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error('Error parsing block: ', {block: blockInfo, error: e});
|
log.error('Error parsing block: ', {
|
||||||
|
block: blockInfo,
|
||||||
|
error: e
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -948,14 +1005,25 @@ class Runtime extends EventEmitter {
|
||||||
if (typeof menuItems !== 'function') {
|
if (typeof menuItems !== 'function') {
|
||||||
const extensionMessageContext = this.makeMessageContextForTarget();
|
const extensionMessageContext = this.makeMessageContextForTarget();
|
||||||
return menuItems.map(item => {
|
return menuItems.map(item => {
|
||||||
const formattedItem = maybeFormatMessage(item, extensionMessageContext);
|
const formattedItem = maybeFormatMessage(
|
||||||
|
item,
|
||||||
|
extensionMessageContext
|
||||||
|
);
|
||||||
switch (typeof formattedItem) {
|
switch (typeof formattedItem) {
|
||||||
case 'string':
|
case 'string':
|
||||||
return [formattedItem, formattedItem];
|
return [formattedItem, formattedItem];
|
||||||
case 'object':
|
case 'object':
|
||||||
return [maybeFormatMessage(item.text, extensionMessageContext), item.value];
|
return [
|
||||||
|
maybeFormatMessage(
|
||||||
|
item.text,
|
||||||
|
extensionMessageContext
|
||||||
|
),
|
||||||
|
item.value
|
||||||
|
];
|
||||||
default:
|
default:
|
||||||
throw new Error(`Can't interpret menu item: ${JSON.stringify(item)}`);
|
throw new Error(
|
||||||
|
`Can't interpret menu item: ${JSON.stringify(item)}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -985,7 +1053,8 @@ class Runtime extends EventEmitter {
|
||||||
colourSecondary: categoryInfo.color2,
|
colourSecondary: categoryInfo.color2,
|
||||||
colourTertiary: categoryInfo.color3,
|
colourTertiary: categoryInfo.color3,
|
||||||
outputShape: menuInfo.acceptReporters ?
|
outputShape: menuInfo.acceptReporters ?
|
||||||
ScratchBlocksConstants.OUTPUT_SHAPE_ROUND : ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE,
|
ScratchBlocksConstants.OUTPUT_SHAPE_ROUND :
|
||||||
|
ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE,
|
||||||
args0: [
|
args0: [
|
||||||
{
|
{
|
||||||
type: 'field_dropdown',
|
type: 'field_dropdown',
|
||||||
|
@ -1027,7 +1096,12 @@ class Runtime extends EventEmitter {
|
||||||
* @param {object} categoryInfo - The category the field belongs to (Used to set its colors)
|
* @param {object} categoryInfo - The category the field belongs to (Used to set its colors)
|
||||||
* @returns {object} - Object to be inserted into scratch-blocks
|
* @returns {object} - Object to be inserted into scratch-blocks
|
||||||
*/
|
*/
|
||||||
_buildCustomFieldTypeForScratchBlocks (fieldName, output, outputShape, categoryInfo) {
|
_buildCustomFieldTypeForScratchBlocks (
|
||||||
|
fieldName,
|
||||||
|
output,
|
||||||
|
outputShape,
|
||||||
|
categoryInfo
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
json: {
|
json: {
|
||||||
type: fieldName,
|
type: fieldName,
|
||||||
|
@ -1114,15 +1188,13 @@ class Runtime extends EventEmitter {
|
||||||
const separatorJSON = {
|
const separatorJSON = {
|
||||||
type: 'field_vertical_separator'
|
type: 'field_vertical_separator'
|
||||||
};
|
};
|
||||||
blockJSON.args0 = [
|
blockJSON.args0 = [iconJSON, separatorJSON];
|
||||||
iconJSON,
|
|
||||||
separatorJSON
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (blockInfo.blockType) {
|
switch (blockInfo.blockType) {
|
||||||
case BlockType.COMMAND:
|
case BlockType.COMMAND:
|
||||||
blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE;
|
blockJSON.outputShape =
|
||||||
|
ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE;
|
||||||
blockJSON.previousStatement = null; // null = available connection; undefined = hat
|
blockJSON.previousStatement = null; // null = available connection; undefined = hat
|
||||||
if (!blockInfo.isTerminal) {
|
if (!blockInfo.isTerminal) {
|
||||||
blockJSON.nextStatement = null; // null = available connection; undefined = terminal
|
blockJSON.nextStatement = null; // null = available connection; undefined = terminal
|
||||||
|
@ -1130,25 +1202,34 @@ class Runtime extends EventEmitter {
|
||||||
break;
|
break;
|
||||||
case BlockType.REPORTER:
|
case BlockType.REPORTER:
|
||||||
blockJSON.output = 'String'; // TODO: distinguish number & string here?
|
blockJSON.output = 'String'; // TODO: distinguish number & string here?
|
||||||
blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_ROUND;
|
blockJSON.outputShape =
|
||||||
|
ScratchBlocksConstants.OUTPUT_SHAPE_ROUND;
|
||||||
break;
|
break;
|
||||||
case BlockType.BOOLEAN:
|
case BlockType.BOOLEAN:
|
||||||
blockJSON.output = 'Boolean';
|
blockJSON.output = 'Boolean';
|
||||||
blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_HEXAGONAL;
|
blockJSON.outputShape =
|
||||||
|
ScratchBlocksConstants.OUTPUT_SHAPE_HEXAGONAL;
|
||||||
break;
|
break;
|
||||||
case BlockType.HAT:
|
case BlockType.HAT:
|
||||||
case BlockType.EVENT:
|
case BlockType.EVENT:
|
||||||
if (!Object.prototype.hasOwnProperty.call(blockInfo, 'isEdgeActivated')) {
|
if (
|
||||||
|
!Object.prototype.hasOwnProperty.call(
|
||||||
|
blockInfo,
|
||||||
|
'isEdgeActivated'
|
||||||
|
)
|
||||||
|
) {
|
||||||
// if absent, this property defaults to true
|
// if absent, this property defaults to true
|
||||||
blockInfo.isEdgeActivated = true;
|
blockInfo.isEdgeActivated = true;
|
||||||
}
|
}
|
||||||
blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE;
|
blockJSON.outputShape =
|
||||||
|
ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE;
|
||||||
blockJSON.nextStatement = null; // null = available connection; undefined = terminal
|
blockJSON.nextStatement = null; // null = available connection; undefined = terminal
|
||||||
break;
|
break;
|
||||||
case BlockType.CONDITIONAL:
|
case BlockType.CONDITIONAL:
|
||||||
case BlockType.LOOP:
|
case BlockType.LOOP:
|
||||||
blockInfo.branchCount = blockInfo.branchCount || 1;
|
blockInfo.branchCount = blockInfo.branchCount || 1;
|
||||||
blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE;
|
blockJSON.outputShape =
|
||||||
|
ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE;
|
||||||
blockJSON.previousStatement = null; // null = available connection; undefined = hat
|
blockJSON.previousStatement = null; // null = available connection; undefined = hat
|
||||||
if (!blockInfo.isTerminal) {
|
if (!blockInfo.isTerminal) {
|
||||||
blockJSON.nextStatement = null; // null = available connection; undefined = terminal
|
blockJSON.nextStatement = null; // null = available connection; undefined = terminal
|
||||||
|
@ -1156,19 +1237,33 @@ class Runtime extends EventEmitter {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const blockText = Array.isArray(blockInfo.text) ? blockInfo.text : [blockInfo.text];
|
const blockText = Array.isArray(blockInfo.text) ?
|
||||||
|
blockInfo.text :
|
||||||
|
[blockInfo.text];
|
||||||
let inTextNum = 0; // text for the next block "arm" is blockText[inTextNum]
|
let inTextNum = 0; // text for the next block "arm" is blockText[inTextNum]
|
||||||
let inBranchNum = 0; // how many branches have we placed into the JSON so far?
|
let inBranchNum = 0; // how many branches have we placed into the JSON so far?
|
||||||
let outLineNum = 0; // used for scratch-blocks `message${outLineNum}` and `args${outLineNum}`
|
let outLineNum = 0; // used for scratch-blocks `message${outLineNum}` and `args${outLineNum}`
|
||||||
const convertPlaceholders = this._convertPlaceholders.bind(this, context);
|
const convertPlaceholders = this._convertPlaceholders.bind(
|
||||||
|
this,
|
||||||
|
context
|
||||||
|
);
|
||||||
const extensionMessageContext = this.makeMessageContextForTarget();
|
const extensionMessageContext = this.makeMessageContextForTarget();
|
||||||
|
|
||||||
// alternate between a block "arm" with text on it and an open slot for a substack
|
// alternate between a block "arm" with text on it and an open slot for a substack
|
||||||
while (inTextNum < blockText.length || inBranchNum < blockInfo.branchCount) {
|
while (
|
||||||
|
inTextNum < blockText.length ||
|
||||||
|
inBranchNum < blockInfo.branchCount
|
||||||
|
) {
|
||||||
if (inTextNum < blockText.length) {
|
if (inTextNum < blockText.length) {
|
||||||
context.outLineNum = outLineNum;
|
context.outLineNum = outLineNum;
|
||||||
const lineText = maybeFormatMessage(blockText[inTextNum], extensionMessageContext);
|
const lineText = maybeFormatMessage(
|
||||||
const convertedText = lineText.replace(/\[(.+?)]/g, convertPlaceholders);
|
blockText[inTextNum],
|
||||||
|
extensionMessageContext
|
||||||
|
);
|
||||||
|
const convertedText = lineText.replace(
|
||||||
|
/\[(.+?)]/g,
|
||||||
|
convertPlaceholders
|
||||||
|
);
|
||||||
if (blockJSON[`message${outLineNum}`]) {
|
if (blockJSON[`message${outLineNum}`]) {
|
||||||
blockJSON[`message${outLineNum}`] += convertedText;
|
blockJSON[`message${outLineNum}`] += convertedText;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1179,10 +1274,14 @@ class Runtime extends EventEmitter {
|
||||||
}
|
}
|
||||||
if (inBranchNum < blockInfo.branchCount) {
|
if (inBranchNum < blockInfo.branchCount) {
|
||||||
blockJSON[`message${outLineNum}`] = '%1';
|
blockJSON[`message${outLineNum}`] = '%1';
|
||||||
blockJSON[`args${outLineNum}`] = [{
|
blockJSON[`args${outLineNum}`] = [
|
||||||
|
{
|
||||||
type: 'input_statement',
|
type: 'input_statement',
|
||||||
name: `SUBSTACK${inBranchNum > 0 ? inBranchNum + 1 : ''}`
|
name: `SUBSTACK${
|
||||||
}];
|
inBranchNum > 0 ? inBranchNum + 1 : ''
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
];
|
||||||
++inBranchNum;
|
++inBranchNum;
|
||||||
++outLineNum;
|
++outLineNum;
|
||||||
}
|
}
|
||||||
|
@ -1196,18 +1295,22 @@ class Runtime extends EventEmitter {
|
||||||
// Add icon to the bottom right of a loop block
|
// Add icon to the bottom right of a loop block
|
||||||
blockJSON[`lastDummyAlign${outLineNum}`] = 'RIGHT';
|
blockJSON[`lastDummyAlign${outLineNum}`] = 'RIGHT';
|
||||||
blockJSON[`message${outLineNum}`] = '%1';
|
blockJSON[`message${outLineNum}`] = '%1';
|
||||||
blockJSON[`args${outLineNum}`] = [{
|
blockJSON[`args${outLineNum}`] = [
|
||||||
|
{
|
||||||
type: 'field_image',
|
type: 'field_image',
|
||||||
src: './static/blocks-media/repeat.svg', // TODO: use a constant or make this configurable?
|
src: './static/blocks-media/repeat.svg', // TODO: use a constant or make this configurable?
|
||||||
width: 24,
|
width: 24,
|
||||||
height: 24,
|
height: 24,
|
||||||
alt: '*', // TODO remove this since we don't use collapsed blocks in scratch
|
alt: '*', // TODO remove this since we don't use collapsed blocks in scratch
|
||||||
flip_rtl: true
|
flip_rtl: true
|
||||||
}];
|
}
|
||||||
|
];
|
||||||
++outLineNum;
|
++outLineNum;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mutation = blockInfo.isDynamic ? `<mutation blockInfo="${xmlEscape(JSON.stringify(blockInfo))}"/>` : '';
|
const mutation = blockInfo.isDynamic ?
|
||||||
|
`<mutation blockInfo="${xmlEscape(JSON.stringify(blockInfo))}"/>` :
|
||||||
|
'';
|
||||||
const inputs = context.inputList.join('');
|
const inputs = context.inputList.join('');
|
||||||
const blockXML = `<block type="${extendedOpcode}">${mutation}${inputs}</block>`;
|
const blockXML = `<block type="${extendedOpcode}">${mutation}${inputs}</block>`;
|
||||||
|
|
||||||
|
@ -1242,13 +1345,22 @@ class Runtime extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
_convertButtonForScratchBlocks (buttonInfo) {
|
_convertButtonForScratchBlocks (buttonInfo) {
|
||||||
// for now we only support these pre-defined callbacks handled in scratch-blocks
|
// for now we only support these pre-defined callbacks handled in scratch-blocks
|
||||||
const supportedCallbackKeys = ['MAKE_A_LIST', 'MAKE_A_PROCEDURE', 'MAKE_A_VARIABLE'];
|
const supportedCallbackKeys = [
|
||||||
|
'MAKE_A_LIST',
|
||||||
|
'MAKE_A_PROCEDURE',
|
||||||
|
'MAKE_A_VARIABLE'
|
||||||
|
];
|
||||||
if (supportedCallbackKeys.indexOf(buttonInfo.func) < 0) {
|
if (supportedCallbackKeys.indexOf(buttonInfo.func) < 0) {
|
||||||
log.error(`Custom button callbacks not supported yet: ${buttonInfo.func}`);
|
log.error(
|
||||||
|
`Custom button callbacks not supported yet: ${buttonInfo.func}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const extensionMessageContext = this.makeMessageContextForTarget();
|
const extensionMessageContext = this.makeMessageContextForTarget();
|
||||||
const buttonText = maybeFormatMessage(buttonInfo.text, extensionMessageContext);
|
const buttonText = maybeFormatMessage(
|
||||||
|
buttonInfo.text,
|
||||||
|
extensionMessageContext
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
info: buttonInfo,
|
info: buttonInfo,
|
||||||
xml: `<button text="${buttonText}" callbackKey="${buttonInfo.func}"></button>`
|
xml: `<button text="${buttonText}" callbackKey="${buttonInfo.func}"></button>`
|
||||||
|
@ -1263,7 +1375,9 @@ class Runtime extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
_constructInlineImageJson (argInfo) {
|
_constructInlineImageJson (argInfo) {
|
||||||
if (!argInfo.dataURI) {
|
if (!argInfo.dataURI) {
|
||||||
log.warn('Missing data URI in extension block with argument type IMAGE');
|
log.warn(
|
||||||
|
'Missing data URI in extension block with argument type IMAGE'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
type: 'field_image',
|
type: 'field_image',
|
||||||
|
@ -1296,8 +1410,13 @@ class Runtime extends EventEmitter {
|
||||||
let argTypeInfo = ArgumentTypeMap[argInfo.type] || {};
|
let argTypeInfo = ArgumentTypeMap[argInfo.type] || {};
|
||||||
|
|
||||||
// Field type not a standard field type, see if extension has registered custom field type
|
// Field type not a standard field type, see if extension has registered custom field type
|
||||||
if (!ArgumentTypeMap[argInfo.type] && context.categoryInfo.customFieldTypes[argInfo.type]) {
|
if (
|
||||||
argTypeInfo = context.categoryInfo.customFieldTypes[argInfo.type].argumentTypeInfo;
|
!ArgumentTypeMap[argInfo.type] &&
|
||||||
|
context.categoryInfo.customFieldTypes[argInfo.type]
|
||||||
|
) {
|
||||||
|
argTypeInfo =
|
||||||
|
context.categoryInfo.customFieldTypes[argInfo.type]
|
||||||
|
.argumentTypeInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start to construct the scratch-blocks style JSON defining how the block should be
|
// Start to construct the scratch-blocks style JSON defining how the block should be
|
||||||
|
@ -1318,8 +1437,14 @@ class Runtime extends EventEmitter {
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultValue =
|
const defaultValue =
|
||||||
typeof argInfo.defaultValue === 'undefined' ? '' :
|
typeof argInfo.defaultValue === 'undefined' ?
|
||||||
xmlEscape(maybeFormatMessage(argInfo.defaultValue, this.makeMessageContextForTarget()).toString());
|
'' :
|
||||||
|
xmlEscape(
|
||||||
|
maybeFormatMessage(
|
||||||
|
argInfo.defaultValue,
|
||||||
|
this.makeMessageContextForTarget()
|
||||||
|
).toString()
|
||||||
|
);
|
||||||
|
|
||||||
if (argTypeInfo.check) {
|
if (argTypeInfo.check) {
|
||||||
// Right now the only type of 'check' we have specifies that the
|
// Right now the only type of 'check' we have specifies that the
|
||||||
|
@ -1335,7 +1460,10 @@ class Runtime extends EventEmitter {
|
||||||
const menuInfo = context.categoryInfo.menuInfo[argInfo.menu];
|
const menuInfo = context.categoryInfo.menuInfo[argInfo.menu];
|
||||||
if (menuInfo.acceptReporters) {
|
if (menuInfo.acceptReporters) {
|
||||||
valueName = placeholder;
|
valueName = placeholder;
|
||||||
shadowType = this._makeExtensionMenuId(argInfo.menu, context.categoryInfo.id);
|
shadowType = this._makeExtensionMenuId(
|
||||||
|
argInfo.menu,
|
||||||
|
context.categoryInfo.id
|
||||||
|
);
|
||||||
fieldName = argInfo.menu;
|
fieldName = argInfo.menu;
|
||||||
} else {
|
} else {
|
||||||
argJSON.type = 'field_dropdown';
|
argJSON.type = 'field_dropdown';
|
||||||
|
@ -1346,8 +1474,11 @@ class Runtime extends EventEmitter {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
valueName = placeholder;
|
valueName = placeholder;
|
||||||
shadowType = (argTypeInfo.shadow && argTypeInfo.shadow.type) || null;
|
shadowType =
|
||||||
fieldName = (argTypeInfo.shadow && argTypeInfo.shadow.fieldName) || null;
|
(argTypeInfo.shadow && argTypeInfo.shadow.type) || null;
|
||||||
|
fieldName =
|
||||||
|
(argTypeInfo.shadow && argTypeInfo.shadow.fieldName) ||
|
||||||
|
null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// <value> is the ScratchBlocks name for a block input.
|
// <value> is the ScratchBlocks name for a block input.
|
||||||
|
@ -1364,7 +1495,9 @@ class Runtime extends EventEmitter {
|
||||||
// A <field> displays a dynamic value: a user-editable text field, a drop-down menu, etc.
|
// A <field> displays a dynamic value: a user-editable text field, a drop-down menu, etc.
|
||||||
// Leave out the field if defaultValue or fieldName are not specified
|
// Leave out the field if defaultValue or fieldName are not specified
|
||||||
if (defaultValue && fieldName) {
|
if (defaultValue && fieldName) {
|
||||||
context.inputList.push(`<field name="${fieldName}">${defaultValue}</field>`);
|
context.inputList.push(
|
||||||
|
`<field name="${fieldName}">${defaultValue}</field>`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shadowType) {
|
if (shadowType) {
|
||||||
|
@ -1377,7 +1510,8 @@ class Runtime extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
const argsName = `args${context.outLineNum}`;
|
const argsName = `args${context.outLineNum}`;
|
||||||
const blockArgs = (context.blockJSON[argsName] = context.blockJSON[argsName] || []);
|
const blockArgs = (context.blockJSON[argsName] =
|
||||||
|
context.blockJSON[argsName] || []);
|
||||||
if (argJSON) blockArgs.push(argJSON);
|
if (argJSON) blockArgs.push(argJSON);
|
||||||
const argNum = blockArgs.length;
|
const argNum = blockArgs.length;
|
||||||
context.argsMap[placeholder] = argNum;
|
context.argsMap[placeholder] = argNum;
|
||||||
|
@ -1419,8 +1553,7 @@ class Runtime extends EventEmitter {
|
||||||
} else if (categoryInfo.blockIconURI) {
|
} else if (categoryInfo.blockIconURI) {
|
||||||
menuIconURI = categoryInfo.blockIconURI;
|
menuIconURI = categoryInfo.blockIconURI;
|
||||||
}
|
}
|
||||||
const menuIconXML = menuIconURI ?
|
const menuIconXML = menuIconURI ? `iconURI="${menuIconURI}"` : '';
|
||||||
`iconURI="${menuIconURI}"` : '';
|
|
||||||
|
|
||||||
let statusButtonXML = '';
|
let statusButtonXML = '';
|
||||||
if (categoryInfo.showStatusButton) {
|
if (categoryInfo.showStatusButton) {
|
||||||
|
@ -1429,8 +1562,11 @@ class Runtime extends EventEmitter {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: categoryInfo.id,
|
id: categoryInfo.id,
|
||||||
xml: `<category name="${name}" id="${categoryInfo.id}" ${statusButtonXML} ${colorXML} ${menuIconXML}>${
|
xml: `<category name="${name}" id="${
|
||||||
paletteBlocks.map(block => block.xml).join('')}</category>`
|
categoryInfo.id
|
||||||
|
}" ${statusButtonXML} ${colorXML} ${menuIconXML}>${paletteBlocks
|
||||||
|
.map(block => block.xml)
|
||||||
|
.join('')}</category>`
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1440,7 +1576,12 @@ class Runtime extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
getBlocksJSON () {
|
getBlocksJSON () {
|
||||||
return this._blockInfo.reduce(
|
return this._blockInfo.reduce(
|
||||||
(result, categoryInfo) => result.concat(categoryInfo.blocks.map(blockInfo => blockInfo.json)), []);
|
(result, categoryInfo) =>
|
||||||
|
result.concat(
|
||||||
|
categoryInfo.blocks.map(blockInfo => blockInfo.json)
|
||||||
|
),
|
||||||
|
[]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1449,7 +1590,8 @@ class Runtime extends EventEmitter {
|
||||||
_initScratchLink () {
|
_initScratchLink () {
|
||||||
// Check that we're actually in a real browser, not Node.js or JSDOM, and we have a valid-looking origin.
|
// Check that we're actually in a real browser, not Node.js or JSDOM, and we have a valid-looking origin.
|
||||||
// note that `if (self?....)` will throw if `self` is undefined, so check for that first!
|
// note that `if (self?....)` will throw if `self` is undefined, so check for that first!
|
||||||
if (typeof self !== 'undefined' &&
|
if (
|
||||||
|
typeof self !== 'undefined' &&
|
||||||
typeof document !== 'undefined' &&
|
typeof document !== 'undefined' &&
|
||||||
document.getElementById &&
|
document.getElementById &&
|
||||||
self.origin &&
|
self.origin &&
|
||||||
|
@ -1462,7 +1604,9 @@ class Runtime extends EventEmitter {
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
// Create a script tag for the Scratch Link browser extension, unless one already exists
|
// Create a script tag for the Scratch Link browser extension, unless one already exists
|
||||||
const scriptElement = document.getElementById('scratch-link-extension-script');
|
const scriptElement = document.getElementById(
|
||||||
|
'scratch-link-extension-script'
|
||||||
|
);
|
||||||
if (!scriptElement) {
|
if (!scriptElement) {
|
||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
script.id = 'scratch-link-extension-script';
|
script.id = 'scratch-link-extension-script';
|
||||||
|
@ -1481,7 +1625,8 @@ class Runtime extends EventEmitter {
|
||||||
* @returns {ScratchLinkSocket} The scratch link socket.
|
* @returns {ScratchLinkSocket} The scratch link socket.
|
||||||
*/
|
*/
|
||||||
getScratchLinkSocket (type) {
|
getScratchLinkSocket (type) {
|
||||||
const factory = this._linkSocketFactory || this._defaultScratchLinkSocketFactory;
|
const factory =
|
||||||
|
this._linkSocketFactory || this._defaultScratchLinkSocketFactory;
|
||||||
return factory(type);
|
return factory(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1501,10 +1646,15 @@ class Runtime extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
_defaultScratchLinkSocketFactory (type) {
|
_defaultScratchLinkSocketFactory (type) {
|
||||||
const Scratch = self.Scratch;
|
const Scratch = self.Scratch;
|
||||||
const ScratchLinkSafariSocket = Scratch && Scratch.ScratchLinkSafariSocket;
|
const ScratchLinkSafariSocket =
|
||||||
|
Scratch && Scratch.ScratchLinkSafariSocket;
|
||||||
// detect this every time in case the user turns on the extension after loading the page
|
// detect this every time in case the user turns on the extension after loading the page
|
||||||
const useSafariSocket = ScratchLinkSafariSocket && ScratchLinkSafariSocket.isSafariHelperCompatible();
|
const useSafariSocket =
|
||||||
return useSafariSocket ? new ScratchLinkSafariSocket(type) : new ScratchLinkWebSocket(type);
|
ScratchLinkSafariSocket &&
|
||||||
|
ScratchLinkSafariSocket.isSafariHelperCompatible();
|
||||||
|
return useSafariSocket ?
|
||||||
|
new ScratchLinkSafariSocket(type) :
|
||||||
|
new ScratchLinkWebSocket(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1593,11 +1743,12 @@ class Runtime extends EventEmitter {
|
||||||
* @return {boolean} True if the op is known to be a edge-activated hat.
|
* @return {boolean} True if the op is known to be a edge-activated hat.
|
||||||
*/
|
*/
|
||||||
getIsEdgeActivatedHat (opcode) {
|
getIsEdgeActivatedHat (opcode) {
|
||||||
return Object.prototype.hasOwnProperty.call(this._hats, opcode) &&
|
return (
|
||||||
this._hats[opcode].edgeActivated;
|
Object.prototype.hasOwnProperty.call(this._hats, opcode) &&
|
||||||
|
this._hats[opcode].edgeActivated
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attach the audio engine
|
* Attach the audio engine
|
||||||
* @param {!AudioEngine} audioEngine The audio engine to attach
|
* @param {!AudioEngine} audioEngine The audio engine to attach
|
||||||
|
@ -1701,10 +1852,10 @@ class Runtime extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
isActiveThread (thread) {
|
isActiveThread (thread) {
|
||||||
return (
|
return (
|
||||||
(
|
|
||||||
thread.stack.length > 0 &&
|
thread.stack.length > 0 &&
|
||||||
thread.status !== Thread.STATUS_DONE) &&
|
thread.status !== Thread.STATUS_DONE &&
|
||||||
this.threads.indexOf(thread) > -1);
|
this.threads.indexOf(thread) > -1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1729,18 +1880,29 @@ class Runtime extends EventEmitter {
|
||||||
* determines whether we show a visual report when turning on the script.
|
* determines whether we show a visual report when turning on the script.
|
||||||
*/
|
*/
|
||||||
toggleScript (topBlockId, opts) {
|
toggleScript (topBlockId, opts) {
|
||||||
opts = Object.assign({
|
opts = Object.assign(
|
||||||
|
{
|
||||||
target: this._editingTarget,
|
target: this._editingTarget,
|
||||||
stackClick: false
|
stackClick: false
|
||||||
}, opts);
|
},
|
||||||
|
opts
|
||||||
|
);
|
||||||
// Remove any existing thread.
|
// Remove any existing thread.
|
||||||
for (let i = 0; i < this.threads.length; i++) {
|
for (let i = 0; i < this.threads.length; i++) {
|
||||||
// Toggling a script that's already running turns it off
|
// Toggling a script that's already running turns it off
|
||||||
if (this.threads[i].topBlock === topBlockId && this.threads[i].status !== Thread.STATUS_DONE) {
|
if (
|
||||||
|
this.threads[i].topBlock === topBlockId &&
|
||||||
|
this.threads[i].status !== Thread.STATUS_DONE
|
||||||
|
) {
|
||||||
const blockContainer = opts.target.blocks;
|
const blockContainer = opts.target.blocks;
|
||||||
const opcode = blockContainer.getOpcode(blockContainer.getBlock(topBlockId));
|
const opcode = blockContainer.getOpcode(
|
||||||
|
blockContainer.getBlock(topBlockId)
|
||||||
|
);
|
||||||
|
|
||||||
if (this.getIsEdgeActivatedHat(opcode) && this.threads[i].stackClick !== opts.stackClick) {
|
if (
|
||||||
|
this.getIsEdgeActivatedHat(opcode) &&
|
||||||
|
this.threads[i].stackClick !== opts.stackClick
|
||||||
|
) {
|
||||||
// Allow edge activated hat thread stack click to coexist with
|
// Allow edge activated hat thread stack click to coexist with
|
||||||
// edge activated hat thread that runs every frame
|
// edge activated hat thread that runs every frame
|
||||||
continue;
|
continue;
|
||||||
|
@ -1762,8 +1924,11 @@ class Runtime extends EventEmitter {
|
||||||
if (!optTarget) optTarget = this._editingTarget;
|
if (!optTarget) optTarget = this._editingTarget;
|
||||||
for (let i = 0; i < this.threads.length; i++) {
|
for (let i = 0; i < this.threads.length; i++) {
|
||||||
// Don't re-add the script if it's already running
|
// Don't re-add the script if it's already running
|
||||||
if (this.threads[i].topBlock === topBlockId && this.threads[i].status !== Thread.STATUS_DONE &&
|
if (
|
||||||
this.threads[i].updateMonitor) {
|
this.threads[i].topBlock === topBlockId &&
|
||||||
|
this.threads[i].status !== Thread.STATUS_DONE &&
|
||||||
|
this.threads[i].updateMonitor
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1801,7 +1966,10 @@ class Runtime extends EventEmitter {
|
||||||
}
|
}
|
||||||
for (let t = targets.length - 1; t >= 0; t--) {
|
for (let t = targets.length - 1; t >= 0; t--) {
|
||||||
const target = targets[t];
|
const target = targets[t];
|
||||||
const scripts = BlocksRuntimeCache.getScripts(target.blocks, opcode);
|
const scripts = BlocksRuntimeCache.getScripts(
|
||||||
|
target.blocks,
|
||||||
|
opcode
|
||||||
|
);
|
||||||
for (let j = 0; j < scripts.length; j++) {
|
for (let j = 0; j < scripts.length; j++) {
|
||||||
f(scripts[j], target);
|
f(scripts[j], target);
|
||||||
}
|
}
|
||||||
|
@ -1815,9 +1983,13 @@ class Runtime extends EventEmitter {
|
||||||
* @param {Target=} optTarget Optionally, a target to restrict to.
|
* @param {Target=} optTarget Optionally, a target to restrict to.
|
||||||
* @return {Array.<Thread>} List of threads started by this function.
|
* @return {Array.<Thread>} List of threads started by this function.
|
||||||
*/
|
*/
|
||||||
startHats (requestedHatOpcode,
|
startHats (requestedHatOpcode, optMatchFields, optTarget) {
|
||||||
optMatchFields, optTarget) {
|
if (
|
||||||
if (!Object.prototype.hasOwnProperty.call(this._hats, requestedHatOpcode)) {
|
!Object.prototype.hasOwnProperty.call(
|
||||||
|
this._hats,
|
||||||
|
requestedHatOpcode
|
||||||
|
)
|
||||||
|
) {
|
||||||
// No known hat with this opcode.
|
// No known hat with this opcode.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1827,16 +1999,18 @@ class Runtime extends EventEmitter {
|
||||||
const hatMeta = instance._hats[requestedHatOpcode];
|
const hatMeta = instance._hats[requestedHatOpcode];
|
||||||
|
|
||||||
for (const opts in optMatchFields) {
|
for (const opts in optMatchFields) {
|
||||||
if (!Object.prototype.hasOwnProperty.call(optMatchFields, opts)) continue;
|
if (!Object.prototype.hasOwnProperty.call(optMatchFields, opts)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
optMatchFields[opts] = optMatchFields[opts].toUpperCase();
|
optMatchFields[opts] = optMatchFields[opts].toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consider all scripts, looking for hats with opcode `requestedHatOpcode`.
|
// Consider all scripts, looking for hats with opcode `requestedHatOpcode`.
|
||||||
this.allScriptsByOpcodeDo(requestedHatOpcode, (script, target) => {
|
this.allScriptsByOpcodeDo(
|
||||||
const {
|
requestedHatOpcode,
|
||||||
blockId: topBlockId,
|
(script, target) => {
|
||||||
fieldsOfInputs: hatFields
|
const {blockId: topBlockId, fieldsOfInputs: hatFields} =
|
||||||
} = script;
|
script;
|
||||||
|
|
||||||
// Match any requested fields.
|
// Match any requested fields.
|
||||||
// For example: ensures that broadcasts match.
|
// For example: ensures that broadcasts match.
|
||||||
|
@ -1844,7 +2018,10 @@ class Runtime extends EventEmitter {
|
||||||
// (i.e., before the predicate can be run) because "broadcast and wait"
|
// (i.e., before the predicate can be run) because "broadcast and wait"
|
||||||
// needs to have a precise collection of started threads.
|
// needs to have a precise collection of started threads.
|
||||||
for (const matchField in optMatchFields) {
|
for (const matchField in optMatchFields) {
|
||||||
if (hatFields[matchField].value !== optMatchFields[matchField]) {
|
if (
|
||||||
|
hatFields[matchField].value !==
|
||||||
|
optMatchFields[matchField]
|
||||||
|
) {
|
||||||
// Field mismatch.
|
// Field mismatch.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1854,11 +2031,15 @@ class Runtime extends EventEmitter {
|
||||||
// If `restartExistingThreads` is true, we should stop
|
// If `restartExistingThreads` is true, we should stop
|
||||||
// any existing threads starting with the top block.
|
// any existing threads starting with the top block.
|
||||||
for (let i = 0; i < this.threads.length; i++) {
|
for (let i = 0; i < this.threads.length; i++) {
|
||||||
if (this.threads[i].target === target &&
|
if (
|
||||||
|
this.threads[i].target === target &&
|
||||||
this.threads[i].topBlock === topBlockId &&
|
this.threads[i].topBlock === topBlockId &&
|
||||||
// stack click threads and hat threads can coexist
|
// stack click threads and hat threads can coexist
|
||||||
!this.threads[i].stackClick) {
|
!this.threads[i].stackClick
|
||||||
newThreads.push(this._restartThread(this.threads[i]));
|
) {
|
||||||
|
newThreads.push(
|
||||||
|
this._restartThread(this.threads[i])
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1866,11 +2047,13 @@ class Runtime extends EventEmitter {
|
||||||
// If `restartExistingThreads` is false, we should
|
// If `restartExistingThreads` is false, we should
|
||||||
// give up if any threads with the top block are running.
|
// give up if any threads with the top block are running.
|
||||||
for (let j = 0; j < this.threads.length; j++) {
|
for (let j = 0; j < this.threads.length; j++) {
|
||||||
if (this.threads[j].target === target &&
|
if (
|
||||||
|
this.threads[j].target === target &&
|
||||||
this.threads[j].topBlock === topBlockId &&
|
this.threads[j].topBlock === topBlockId &&
|
||||||
// stack click threads and hat threads can coexist
|
// stack click threads and hat threads can coexist
|
||||||
!this.threads[j].stackClick &&
|
!this.threads[j].stackClick &&
|
||||||
this.threads[j].status !== Thread.STATUS_DONE) {
|
this.threads[j].status !== Thread.STATUS_DONE
|
||||||
|
) {
|
||||||
// Some thread is already running.
|
// Some thread is already running.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1878,7 +2061,9 @@ class Runtime extends EventEmitter {
|
||||||
}
|
}
|
||||||
// Start the thread with this top block.
|
// Start the thread with this top block.
|
||||||
newThreads.push(this._pushThread(topBlockId, target));
|
newThreads.push(this._pushThread(topBlockId, target));
|
||||||
}, optTarget);
|
},
|
||||||
|
optTarget
|
||||||
|
);
|
||||||
// For compatibility with Scratch 2, edge triggered hats need to be processed before
|
// For compatibility with Scratch 2, edge triggered hats need to be processed before
|
||||||
// threads are stepped. See ScratchRuntime.as for original implementation
|
// threads are stepped. See ScratchRuntime.as for original implementation
|
||||||
newThreads.forEach(thread => {
|
newThreads.forEach(thread => {
|
||||||
|
@ -1888,7 +2073,6 @@ class Runtime extends EventEmitter {
|
||||||
return newThreads;
|
return newThreads;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispose all targets. Return to clean state.
|
* Dispose all targets. Return to clean state.
|
||||||
*/
|
*/
|
||||||
|
@ -1920,8 +2104,10 @@ class Runtime extends EventEmitter {
|
||||||
const newCloudDataManager = cloudDataManager();
|
const newCloudDataManager = cloudDataManager();
|
||||||
this.hasCloudData = newCloudDataManager.hasCloudVariables;
|
this.hasCloudData = newCloudDataManager.hasCloudVariables;
|
||||||
this.canAddCloudVariable = newCloudDataManager.canAddCloudVariable;
|
this.canAddCloudVariable = newCloudDataManager.canAddCloudVariable;
|
||||||
this.addCloudVariable = this._initializeAddCloudVariable(newCloudDataManager);
|
this.addCloudVariable =
|
||||||
this.removeCloudVariable = this._initializeRemoveCloudVariable(newCloudDataManager);
|
this._initializeAddCloudVariable(newCloudDataManager);
|
||||||
|
this.removeCloudVariable =
|
||||||
|
this._initializeRemoveCloudVariable(newCloudDataManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1953,7 +2139,10 @@ class Runtime extends EventEmitter {
|
||||||
newIndex = this.executableTargets.length;
|
newIndex = this.executableTargets.length;
|
||||||
}
|
}
|
||||||
if (newIndex <= 0) {
|
if (newIndex <= 0) {
|
||||||
if (this.executableTargets.length > 0 && this.executableTargets[0].isStage) {
|
if (
|
||||||
|
this.executableTargets.length > 0 &&
|
||||||
|
this.executableTargets[0].isStage
|
||||||
|
) {
|
||||||
newIndex = 1;
|
newIndex = 1;
|
||||||
} else {
|
} else {
|
||||||
newIndex = 0;
|
newIndex = 0;
|
||||||
|
@ -2033,7 +2222,10 @@ class Runtime extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
const newRunId = uuid.v1();
|
const newRunId = uuid.v1();
|
||||||
this.storage.scratchFetch.setMetadata(this.storage.scratchFetch.RequestMetadata.RunId, newRunId);
|
this.storage.scratchFetch.setMetadata(
|
||||||
|
this.storage.scratchFetch.RequestMetadata.RunId,
|
||||||
|
newRunId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2062,8 +2254,13 @@ class Runtime extends EventEmitter {
|
||||||
const newTargets = [];
|
const newTargets = [];
|
||||||
for (let i = 0; i < this.targets.length; i++) {
|
for (let i = 0; i < this.targets.length; i++) {
|
||||||
this.targets[i].onStopAll();
|
this.targets[i].onStopAll();
|
||||||
if (Object.prototype.hasOwnProperty.call(this.targets[i], 'isOriginal') &&
|
if (
|
||||||
!this.targets[i].isOriginal) {
|
Object.prototype.hasOwnProperty.call(
|
||||||
|
this.targets[i],
|
||||||
|
'isOriginal'
|
||||||
|
) &&
|
||||||
|
!this.targets[i].isOriginal
|
||||||
|
) {
|
||||||
this.targets[i].dispose();
|
this.targets[i].dispose();
|
||||||
} else {
|
} else {
|
||||||
newTargets.push(this.targets[i]);
|
newTargets.push(this.targets[i]);
|
||||||
|
@ -2097,7 +2294,9 @@ class Runtime extends EventEmitter {
|
||||||
|
|
||||||
// Find all edge-activated hats, and add them to threads to be evaluated.
|
// Find all edge-activated hats, and add them to threads to be evaluated.
|
||||||
for (const hatType in this._hats) {
|
for (const hatType in this._hats) {
|
||||||
if (!Object.prototype.hasOwnProperty.call(this._hats, hatType)) continue;
|
if (!Object.prototype.hasOwnProperty.call(this._hats, hatType)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const hat = this._hats[hatType];
|
const hat = this._hats[hatType];
|
||||||
if (hat.edgeActivated) {
|
if (hat.edgeActivated) {
|
||||||
this.startHats(hatType);
|
this.startHats(hatType);
|
||||||
|
@ -2107,7 +2306,9 @@ class Runtime extends EventEmitter {
|
||||||
this._pushMonitors();
|
this._pushMonitors();
|
||||||
if (this.profiler !== null) {
|
if (this.profiler !== null) {
|
||||||
if (stepThreadsProfilerId === -1) {
|
if (stepThreadsProfilerId === -1) {
|
||||||
stepThreadsProfilerId = this.profiler.idByName('Sequencer.stepThreads');
|
stepThreadsProfilerId = this.profiler.idByName(
|
||||||
|
'Sequencer.stepThreads'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.profiler.start(stepThreadsProfilerId);
|
this.profiler.start(stepThreadsProfilerId);
|
||||||
}
|
}
|
||||||
|
@ -2119,8 +2320,10 @@ class Runtime extends EventEmitter {
|
||||||
// Add done threads so that even if a thread finishes within 1 frame, the green
|
// Add done threads so that even if a thread finishes within 1 frame, the green
|
||||||
// flag will still indicate that a script ran.
|
// flag will still indicate that a script ran.
|
||||||
this._emitProjectRunStatus(
|
this._emitProjectRunStatus(
|
||||||
this.threads.length + doneThreads.length -
|
this.threads.length +
|
||||||
this._getMonitorThreadCount([...this.threads, ...doneThreads]));
|
doneThreads.length -
|
||||||
|
this._getMonitorThreadCount([...this.threads, ...doneThreads])
|
||||||
|
);
|
||||||
// Store threads that completed this iteration for testing and other
|
// Store threads that completed this iteration for testing and other
|
||||||
// internal purposes.
|
// internal purposes.
|
||||||
this._lastStepDoneThreads = doneThreads;
|
this._lastStepDoneThreads = doneThreads;
|
||||||
|
@ -2128,7 +2331,8 @@ class Runtime extends EventEmitter {
|
||||||
// @todo: Only render when this.redrawRequested or clones rendered.
|
// @todo: Only render when this.redrawRequested or clones rendered.
|
||||||
if (this.profiler !== null) {
|
if (this.profiler !== null) {
|
||||||
if (rendererDrawProfilerId === -1) {
|
if (rendererDrawProfilerId === -1) {
|
||||||
rendererDrawProfilerId = this.profiler.idByName('RenderWebGL.draw');
|
rendererDrawProfilerId =
|
||||||
|
this.profiler.idByName('RenderWebGL.draw');
|
||||||
}
|
}
|
||||||
this.profiler.start(rendererDrawProfilerId);
|
this.profiler.start(rendererDrawProfilerId);
|
||||||
}
|
}
|
||||||
|
@ -2139,7 +2343,10 @@ class Runtime extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._refreshTargets) {
|
if (this._refreshTargets) {
|
||||||
this.emit(Runtime.TARGETS_UPDATE, false /* Don't emit project changed */);
|
this.emit(
|
||||||
|
Runtime.TARGETS_UPDATE,
|
||||||
|
false /* Don't emit project changed */
|
||||||
|
);
|
||||||
this._refreshTargets = false;
|
this._refreshTargets = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2226,12 +2433,12 @@ class Runtime extends EventEmitter {
|
||||||
if (target === this._editingTarget) {
|
if (target === this._editingTarget) {
|
||||||
const blockForThread = thread.blockGlowInFrame;
|
const blockForThread = thread.blockGlowInFrame;
|
||||||
if (thread.requestScriptGlowInFrame || thread.stackClick) {
|
if (thread.requestScriptGlowInFrame || thread.stackClick) {
|
||||||
let script = target.blocks.getTopLevelScript(blockForThread);
|
let script =
|
||||||
|
target.blocks.getTopLevelScript(blockForThread);
|
||||||
if (!script) {
|
if (!script) {
|
||||||
// Attempt to find in flyout blocks.
|
// Attempt to find in flyout blocks.
|
||||||
script = this.flyoutBlocks.getTopLevelScript(
|
script =
|
||||||
blockForThread
|
this.flyoutBlocks.getTopLevelScript(blockForThread);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (script) {
|
if (script) {
|
||||||
requestedGlowsThisFrame.push(script);
|
requestedGlowsThisFrame.push(script);
|
||||||
|
@ -2349,7 +2556,8 @@ class Runtime extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
requestAddMonitor (monitor) {
|
requestAddMonitor (monitor) {
|
||||||
const id = monitor.get('id');
|
const id = monitor.get('id');
|
||||||
if (!this.requestUpdateMonitor(monitor)) { // update monitor if it exists in the state
|
if (!this.requestUpdateMonitor(monitor)) {
|
||||||
|
// update monitor if it exists in the state
|
||||||
// if the monitor did not exist in the state, add it
|
// if the monitor did not exist in the state, add it
|
||||||
this._monitorState = this._monitorState.set(id, monitor);
|
this._monitorState = this._monitorState.set(id, monitor);
|
||||||
}
|
}
|
||||||
|
@ -2367,12 +2575,15 @@ class Runtime extends EventEmitter {
|
||||||
if (this._monitorState.has(id)) {
|
if (this._monitorState.has(id)) {
|
||||||
this._monitorState =
|
this._monitorState =
|
||||||
// Use mergeWith here to prevent undefined values from overwriting existing ones
|
// Use mergeWith here to prevent undefined values from overwriting existing ones
|
||||||
this._monitorState.set(id, this._monitorState.get(id).mergeWith((prev, next) => {
|
this._monitorState.set(
|
||||||
|
id,
|
||||||
|
this._monitorState.get(id).mergeWith((prev, next) => {
|
||||||
if (typeof next === 'undefined' || next === null) {
|
if (typeof next === 'undefined' || next === null) {
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
return next;
|
return next;
|
||||||
}, monitor));
|
}, monitor)
|
||||||
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -2393,10 +2604,12 @@ class Runtime extends EventEmitter {
|
||||||
* @return {boolean} true if monitor exists and was updated, false otherwise
|
* @return {boolean} true if monitor exists and was updated, false otherwise
|
||||||
*/
|
*/
|
||||||
requestHideMonitor (monitorId) {
|
requestHideMonitor (monitorId) {
|
||||||
return this.requestUpdateMonitor(new Map([
|
return this.requestUpdateMonitor(
|
||||||
|
new Map([
|
||||||
['id', monitorId],
|
['id', monitorId],
|
||||||
['visible', false]
|
['visible', false]
|
||||||
]));
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2406,10 +2619,12 @@ class Runtime extends EventEmitter {
|
||||||
* @return {boolean} true if monitor exists and was updated, false otherwise
|
* @return {boolean} true if monitor exists and was updated, false otherwise
|
||||||
*/
|
*/
|
||||||
requestShowMonitor (monitorId) {
|
requestShowMonitor (monitorId) {
|
||||||
return this.requestUpdateMonitor(new Map([
|
return this.requestUpdateMonitor(
|
||||||
|
new Map([
|
||||||
['id', monitorId],
|
['id', monitorId],
|
||||||
['visible', true]
|
['visible', true]
|
||||||
]));
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2418,7 +2633,9 @@ class Runtime extends EventEmitter {
|
||||||
* @param {!string} targetId Remove all monitors with given target ID.
|
* @param {!string} targetId Remove all monitors with given target ID.
|
||||||
*/
|
*/
|
||||||
requestRemoveMonitorByTargetId (targetId) {
|
requestRemoveMonitorByTargetId (targetId) {
|
||||||
this._monitorState = this._monitorState.filterNot(value => value.targetId === targetId);
|
this._monitorState = this._monitorState.filterNot(
|
||||||
|
value => value.targetId === targetId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2538,7 +2755,10 @@ class Runtime extends EventEmitter {
|
||||||
getAllVarNamesOfType (varType) {
|
getAllVarNamesOfType (varType) {
|
||||||
let varNames = [];
|
let varNames = [];
|
||||||
for (const target of this.targets) {
|
for (const target of this.targets) {
|
||||||
const targetVarNames = target.getAllVariableNamesInScopeByType(varType, true);
|
const targetVarNames = target.getAllVariableNamesInScopeByType(
|
||||||
|
varType,
|
||||||
|
true
|
||||||
|
);
|
||||||
varNames = varNames.concat(targetVarNames);
|
varNames = varNames.concat(targetVarNames);
|
||||||
}
|
}
|
||||||
return varNames;
|
return varNames;
|
||||||
|
@ -2579,7 +2799,8 @@ class Runtime extends EventEmitter {
|
||||||
* @return {Variable} The new variable that was created.
|
* @return {Variable} The new variable that was created.
|
||||||
*/
|
*/
|
||||||
createNewGlobalVariable (variableName, optVarId, optVarType) {
|
createNewGlobalVariable (variableName, optVarId, optVarType) {
|
||||||
const varType = (typeof optVarType === 'string') ? optVarType : Variable.SCALAR_TYPE;
|
const varType =
|
||||||
|
typeof optVarType === 'string' ? optVarType : Variable.SCALAR_TYPE;
|
||||||
const allVariableNames = this.getAllVarNamesOfType(varType);
|
const allVariableNames = this.getAllVarNamesOfType(varType);
|
||||||
const newName = StringUtil.unusedName(variableName, allVariableNames);
|
const newName = StringUtil.unusedName(variableName, allVariableNames);
|
||||||
const variable = new Variable(optVarId || uid(), newName, varType);
|
const variable = new Variable(optVarId || uid(), newName, varType);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue