mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-23 06:23:37 -05:00
Allow extensions to make buttons
This commit is contained in:
parent
445ee46984
commit
bed54bae1f
3 changed files with 89 additions and 33 deletions
|
@ -123,16 +123,6 @@ const cloudDataManager = () => {
|
|||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Predefined "Converted block info" for a separator between blocks in a block category
|
||||
* @type {ConvertedBlockInfo}
|
||||
*/
|
||||
const ConvertedSeparator = {
|
||||
info: {},
|
||||
json: null,
|
||||
xml: '<sep gap="36"/>'
|
||||
};
|
||||
|
||||
/**
|
||||
* Numeric ID for Runtime._step in Profiler instances.
|
||||
* @type {number}
|
||||
|
@ -866,22 +856,20 @@ class Runtime extends EventEmitter {
|
|||
}
|
||||
|
||||
for (const blockInfo of extensionInfo.blocks) {
|
||||
if (blockInfo === '---') {
|
||||
categoryInfo.blocks.push(ConvertedSeparator);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const convertedBlock = this._convertForScratchBlocks(blockInfo, categoryInfo);
|
||||
const opcode = convertedBlock.json.type;
|
||||
categoryInfo.blocks.push(convertedBlock);
|
||||
if (blockInfo.blockType !== BlockType.EVENT) {
|
||||
this._primitives[opcode] = convertedBlock.info.func;
|
||||
}
|
||||
if (blockInfo.blockType === BlockType.EVENT || blockInfo.blockType === BlockType.HAT) {
|
||||
this._hats[opcode] = {
|
||||
edgeActivated: blockInfo.isEdgeActivated,
|
||||
restartExistingThreads: blockInfo.shouldRestartExistingThreads
|
||||
};
|
||||
if (convertedBlock.json) {
|
||||
const opcode = convertedBlock.json.type;
|
||||
if (blockInfo.blockType !== BlockType.EVENT) {
|
||||
this._primitives[opcode] = convertedBlock.info.func;
|
||||
}
|
||||
if (blockInfo.blockType === BlockType.EVENT || blockInfo.blockType === BlockType.HAT) {
|
||||
this._hats[opcode] = {
|
||||
edgeActivated: blockInfo.isEdgeActivated,
|
||||
restartExistingThreads: blockInfo.shouldRestartExistingThreads
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
log.error('Error parsing block: ', {block: blockInfo, error: e});
|
||||
|
@ -986,6 +974,25 @@ class Runtime extends EventEmitter {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert ExtensionBlockMetadata into data ready for scratch-blocks.
|
||||
* @param {ExtensionBlockMetadata} blockInfo - the block info to convert
|
||||
* @param {CategoryInfo} categoryInfo - the category for this block
|
||||
* @returns {ConvertedBlockInfo} - the converted & original block information
|
||||
* @private
|
||||
*/
|
||||
_convertForScratchBlocks (blockInfo, categoryInfo) {
|
||||
if (blockInfo === '---') {
|
||||
return this._convertSeparatorForScratchBlocks();
|
||||
}
|
||||
|
||||
if (blockInfo.blockType === BlockType.BUTTON) {
|
||||
return this._convertButtonForScratchBlocks(blockInfo);
|
||||
}
|
||||
|
||||
return this._convertBlockForScratchBlocks(blockInfo, categoryInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert ExtensionBlockMetadata into scratch-blocks JSON & XML, and generate a proxy function.
|
||||
* @param {ExtensionBlockMetadata} blockInfo - the block to convert
|
||||
|
@ -993,7 +1000,7 @@ class Runtime extends EventEmitter {
|
|||
* @returns {ConvertedBlockInfo} - the converted & original block information
|
||||
* @private
|
||||
*/
|
||||
_convertForScratchBlocks (blockInfo, categoryInfo) {
|
||||
_convertBlockForScratchBlocks (blockInfo, categoryInfo) {
|
||||
const extendedOpcode = `${categoryInfo.id}_${blockInfo.opcode}`;
|
||||
|
||||
const blockJSON = {
|
||||
|
@ -1135,6 +1142,43 @@ class Runtime extends EventEmitter {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a separator between blocks categories or sub-categories.
|
||||
* @param {ExtensionBlockMetadata} blockInfo - the block to convert
|
||||
* @param {CategoryInfo} categoryInfo - the category for this block
|
||||
* @returns {ConvertedBlockInfo} - the converted & original block information
|
||||
* @private
|
||||
*/
|
||||
_convertSeparatorForScratchBlocks (blockInfo) {
|
||||
return {
|
||||
info: blockInfo,
|
||||
xml: '<sep gap="36"/>'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a button for scratch-blocks. A button has no opcode but specifies a callback name in the `func` field.
|
||||
* @param {ExtensionBlockMetadata} buttonInfo - the button to convert
|
||||
* @property {string} func - the callback name
|
||||
* @param {CategoryInfo} categoryInfo - the category for this button
|
||||
* @returns {ConvertedBlockInfo} - the converted & original button information
|
||||
* @private
|
||||
*/
|
||||
_convertButtonForScratchBlocks (buttonInfo) {
|
||||
// for now we only support these pre-defined callbacks handled in scratch-blocks
|
||||
const supportedCallbackKeys = ['CREATE_LIST', 'CREATE_PROCEDURE', 'CREATE_VARIABLE'];
|
||||
if (supportedCallbackKeys.indexOf(buttonInfo.func) < 0) {
|
||||
log.error(`Custom button callbacks not supported yet: ${buttonInfo.func}`);
|
||||
}
|
||||
|
||||
const extensionMessageContext = this.makeMessageContextForTarget();
|
||||
const buttonText = maybeFormatMessage(buttonInfo.text, extensionMessageContext);
|
||||
return {
|
||||
info: buttonInfo,
|
||||
xml: `<button text="${buttonText}" callbackKey="${buttonInfo.func}"></button>`
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for _convertForScratchBlocks which handles linearization of argument placeholders. Called as a callback
|
||||
* from string#replace. In addition to the return value the JSON and XML items in the context will be filled.
|
||||
|
|
|
@ -8,6 +8,11 @@ const BlockType = {
|
|||
*/
|
||||
BOOLEAN: 'Boolean',
|
||||
|
||||
/**
|
||||
* A button (not an actual block) for some special action, like making a variable
|
||||
*/
|
||||
BUTTON: 'button',
|
||||
|
||||
/**
|
||||
* Command block
|
||||
*/
|
||||
|
|
|
@ -375,16 +375,24 @@ class ExtensionManager {
|
|||
blockAllThreads: false,
|
||||
arguments: {}
|
||||
}, blockInfo);
|
||||
blockInfo.opcode = this._sanitizeID(blockInfo.opcode);
|
||||
blockInfo.opcode = blockInfo.opcode && this._sanitizeID(blockInfo.opcode);
|
||||
blockInfo.text = blockInfo.text || blockInfo.opcode;
|
||||
|
||||
if (blockInfo.blockType !== BlockType.EVENT) {
|
||||
switch (blockInfo.blockType) {
|
||||
case BlockType.EVENT:
|
||||
if (blockInfo.func) {
|
||||
log.warn(`Ignoring function "${blockInfo.func}" for event block ${blockInfo.opcode}`);
|
||||
}
|
||||
break;
|
||||
case BlockType.BUTTON:
|
||||
if (blockInfo.opcode) {
|
||||
log.warn(`Ignoring opcode "${blockInfo.opcode}" for button with text: ${blockInfo.text}`);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
blockInfo.func = blockInfo.func ? this._sanitizeID(blockInfo.func) : blockInfo.opcode;
|
||||
|
||||
/**
|
||||
* This is only here because the VM performs poorly when blocks return promises.
|
||||
* @TODO make it possible for the VM to resolve a promise and continue during the same Scratch "tick"
|
||||
*/
|
||||
// Avoid promise overhead if possible
|
||||
if (dispatch._isRemoteService(serviceName)) {
|
||||
blockInfo.func = dispatch.call.bind(dispatch, serviceName, blockInfo.func);
|
||||
} else {
|
||||
|
@ -392,12 +400,11 @@ class ExtensionManager {
|
|||
const func = serviceObject[blockInfo.func];
|
||||
if (func) {
|
||||
blockInfo.func = func.bind(serviceObject);
|
||||
} else if (blockInfo.blockType !== BlockType.EVENT) {
|
||||
} else {
|
||||
throw new Error(`Could not find extension block function called ${blockInfo.func}`);
|
||||
}
|
||||
}
|
||||
} else if (blockInfo.func) {
|
||||
log.warn(`Ignoring function "${blockInfo.func}" for event block ${blockInfo.opcode}`);
|
||||
break;
|
||||
}
|
||||
|
||||
return blockInfo;
|
||||
|
|
Loading…
Reference in a new issue