mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-24 06:52:40 -05:00
Make loadExtensionURL
handle built-in extensions
WeDo2 and Pen blocks have been converted to internal extensions, and can now be loaded by giving `loadExtensionURL` the string 'pen' or 'wedo2' instead of an actual URL.
This commit is contained in:
parent
6757fb6de9
commit
e9aed49a05
4 changed files with 207 additions and 26 deletions
|
@ -237,7 +237,7 @@ class Scratch3PenBlocks {
|
|||
}
|
||||
|
||||
/**
|
||||
* @returns {{id: string, name: string, blocks: []}} metadata for this extension and its blocks.
|
||||
* @returns {object} metadata for this extension and its blocks.
|
||||
*/
|
||||
getInfo () {
|
||||
return {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
const ArgumentType = require('../extension-support/argument-type');
|
||||
const BlockType = require('../extension-support/block-type');
|
||||
const color = require('../util/color');
|
||||
const log = require('../util/log');
|
||||
|
||||
|
@ -398,6 +400,183 @@ class Scratch3WeDo2Blocks {
|
|||
this.runtime.HACK_WeDo2Blocks = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {object} metadata for this extension and its blocks.
|
||||
*/
|
||||
getInfo () {
|
||||
return {
|
||||
id: 'wedo2',
|
||||
name: 'WeDo 2.0',
|
||||
blocks: [
|
||||
{
|
||||
opcode: 'motorOnFor',
|
||||
text: 'turn [MOTOR_ID] on for [DURATION] seconds',
|
||||
blockType: BlockType.COMMAND,
|
||||
arguments: {
|
||||
MOTOR_ID: {
|
||||
type: ArgumentType.STRING,
|
||||
menu: 'motorID',
|
||||
defaultValue: MotorID.DEFAULT
|
||||
},
|
||||
DURATION: {
|
||||
type: ArgumentType.NUMBER,
|
||||
defaultValue: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
opcode: 'motorOn',
|
||||
text: 'turn [MOTOR_ID] on',
|
||||
blockType: BlockType.COMMAND,
|
||||
arguments: {
|
||||
MOTOR_ID: {
|
||||
type: ArgumentType.STRING,
|
||||
menu: 'motorID',
|
||||
defaultValue: MotorID.DEFAULT
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
opcode: 'motorOff',
|
||||
text: 'turn [MOTOR_ID] off',
|
||||
blockType: BlockType.COMMAND,
|
||||
arguments: {
|
||||
MOTOR_ID: {
|
||||
type: ArgumentType.STRING,
|
||||
menu: 'motorID',
|
||||
defaultValue: MotorID.DEFAULT
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
opcode: 'startMotorPower',
|
||||
text: 'set [MOTOR_ID] power to [POWER]',
|
||||
blockType: BlockType.COMMAND,
|
||||
arguments: {
|
||||
MOTOR_ID: {
|
||||
type: ArgumentType.STRING,
|
||||
menu: 'motorID',
|
||||
defaultValue: MotorID.DEFAULT
|
||||
},
|
||||
POWER: {
|
||||
type: ArgumentType.NUMBER,
|
||||
defaultValue: 100
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
opcode: 'startMotorDirection',
|
||||
text: 'set [MOTOR_ID] direction to [DIRECTION]',
|
||||
blockType: BlockType.COMMAND,
|
||||
arguments: {
|
||||
MOTOR_ID: {
|
||||
type: ArgumentType.STRING,
|
||||
menu: 'motorID',
|
||||
defaultValue: MotorID.DEFAULT
|
||||
},
|
||||
DIRECTION: {
|
||||
type: ArgumentType.STRING,
|
||||
menu: 'motorDirection',
|
||||
defaultValue: MotorDirection.FORWARD
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
opcode: 'setLightHue',
|
||||
text: 'set light color to [HUE]',
|
||||
blockType: BlockType.COMMAND,
|
||||
arguments: {
|
||||
HUE: {
|
||||
type: ArgumentType.NUMBER,
|
||||
defaultValue: 50
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
opcode: 'playNoteFor',
|
||||
text: 'play note [NOTE] for [DURATION] seconds',
|
||||
blockType: BlockType.COMMAND,
|
||||
arguments: {
|
||||
NOTE: {
|
||||
type: ArgumentType.NUMBER, // TODO: ArgumentType.MIDI_NOTE?
|
||||
defaultValue: 60
|
||||
},
|
||||
DURATION: {
|
||||
type: ArgumentType.NUMBER,
|
||||
defaultValue: 0.5
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
opcode: 'whenDistance',
|
||||
text: 'when distance [OP] [REFERENCE]',
|
||||
blockType: BlockType.HAT,
|
||||
arguments: {
|
||||
OP: {
|
||||
type: ArgumentType.STRING,
|
||||
menu: 'lessMore',
|
||||
defaultValue: '<'
|
||||
},
|
||||
REFERENCE: {
|
||||
type: ArgumentType.NUMBER,
|
||||
defaultValue: 50
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
opcode: 'whenTilted',
|
||||
text: 'when tilted [DIRECTION]',
|
||||
func: 'isTilted',
|
||||
blockType: BlockType.HAT,
|
||||
arguments: {
|
||||
DIRECTION: {
|
||||
type: ArgumentType.STRING,
|
||||
menu: 'tiltDirectionAny',
|
||||
defaultValue: TiltDirection.ANY
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
opcode: 'getDistance',
|
||||
text: 'distance',
|
||||
blockType: BlockType.REPORTER
|
||||
},
|
||||
{
|
||||
opcode: 'isTilted',
|
||||
text: 'tilted [DIRECTION]?',
|
||||
blockType: BlockType.REPORTER,
|
||||
arguments: {
|
||||
DIRECTION: {
|
||||
type: ArgumentType.STRING,
|
||||
menu: 'tiltDirectionAny',
|
||||
defaultValue: TiltDirection.ANY
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
opcode: 'getTiltAngle',
|
||||
text: 'tilt angle [DIRECTION]',
|
||||
blockType: BlockType.REPORTER,
|
||||
arguments: {
|
||||
DIRECTION: {
|
||||
type: ArgumentType.STRING,
|
||||
menu: 'tiltDirection',
|
||||
defaultValue: TiltDirection.UP
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
menus: {
|
||||
motorID: [MotorID.DEFAULT, MotorID.A, MotorID.B, MotorID.ALL],
|
||||
motorDirection: [MotorDirection.FORWARD, MotorDirection.BACKWARD, MotorDirection.REVERSE],
|
||||
tiltDirection: [TiltDirection.UP, TiltDirection.DOWN, TiltDirection.LEFT, TiltDirection.RIGHT],
|
||||
tiltDirectionAny:
|
||||
[TiltDirection.UP, TiltDirection.DOWN, TiltDirection.LEFT, TiltDirection.RIGHT, TiltDirection.ANY],
|
||||
lessMore: ['<', '>']
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the Device Manager client to attempt to connect to a WeDo 2.0 device.
|
||||
*/
|
||||
|
@ -427,27 +606,6 @@ class Scratch3WeDo2Blocks {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the block primitives implemented by this package.
|
||||
* @return {object.<string, Function>} Mapping of opcode to Function.
|
||||
*/
|
||||
getPrimitives () {
|
||||
return {
|
||||
wedo2_motorOnFor: this.motorOnFor,
|
||||
wedo2_motorOn: this.motorOn,
|
||||
wedo2_motorOff: this.motorOff,
|
||||
wedo2_startMotorPower: this.startMotorPower,
|
||||
wedo2_setMotorDirection: this.setMotorDirection,
|
||||
wedo2_setLightHue: this.setLightHue,
|
||||
wedo2_playNoteFor: this.playNoteFor,
|
||||
wedo2_whenDistance: this.whenDistance,
|
||||
wedo2_whenTilted: this.whenTilted,
|
||||
wedo2_getDistance: this.getDistance,
|
||||
wedo2_isTilted: this.isTilted,
|
||||
wedo2_getTiltAngle: this.getTiltAngle
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn specified motor(s) on for a specified duration.
|
||||
* @param {object} args - the block's arguments.
|
||||
|
|
|
@ -3,6 +3,16 @@ const log = require('../util/log');
|
|||
|
||||
const BlockType = require('./block-type');
|
||||
|
||||
// These extensions are currently built into the VM repository but should not be loaded at startup.
|
||||
// TODO: move these out into a separate repository?
|
||||
// TODO: change extension spec so that library info, including extension ID, can be collected through static methods
|
||||
const Scratch3PenBlocks = require('../blocks/scratch3_pen');
|
||||
const Scratch3WeDo2Blocks = require('../blocks/scratch3_wedo2');
|
||||
const builtinExtensions = {
|
||||
pen: Scratch3PenBlocks,
|
||||
wedo2: Scratch3WeDo2Blocks
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {object} ArgumentInfo - Information about an extension block argument
|
||||
* @property {ArgumentType} type - the type of value this argument can take
|
||||
|
@ -39,7 +49,7 @@ const BlockType = require('./block-type');
|
|||
*/
|
||||
|
||||
class ExtensionManager {
|
||||
constructor () {
|
||||
constructor (runtime) {
|
||||
/**
|
||||
* The ID number to provide to the next extension worker.
|
||||
* @type {int}
|
||||
|
@ -60,17 +70,30 @@ class ExtensionManager {
|
|||
*/
|
||||
this.pendingWorkers = [];
|
||||
|
||||
/**
|
||||
* Keep a reference to the runtime so we can construct internal extension objects.
|
||||
* TODO: remove this in favor of extensions accessing the runtime as a service.
|
||||
* @type {Runtime}
|
||||
*/
|
||||
this.runtime = runtime;
|
||||
|
||||
dispatch.setService('extensions', this).catch(e => {
|
||||
log.error(`ExtensionManager was unable to register extension service: ${JSON.stringify(e)}`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an extension by URL
|
||||
* @param {string} extensionURL - the URL for the extension to load
|
||||
* Load an extension by URL or internal extension ID
|
||||
* @param {string} extensionURL - the URL for the extension to load OR the ID of an internal extension
|
||||
* @returns {Promise} resolved once the extension is loaded and initialized or rejected on failure
|
||||
*/
|
||||
loadExtensionURL (extensionURL) {
|
||||
if (builtinExtensions.hasOwnProperty(extensionURL)) {
|
||||
const extension = builtinExtensions[extensionURL];
|
||||
const extensionInstance = new extension(this.runtime);
|
||||
return this._registerInternalExtension(extensionInstance);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// If we `require` this at the global level it breaks non-webpack targets, including tests
|
||||
const ExtensionWorker = require('worker-loader?name=extension-worker.js!./extension-worker');
|
||||
|
|
|
@ -68,7 +68,7 @@ class VirtualMachine extends EventEmitter {
|
|||
this.emit(Runtime.EXTENSION_ADDED, blocksInfo);
|
||||
});
|
||||
|
||||
this.extensionManager = new ExtensionManager();
|
||||
this.extensionManager = new ExtensionManager(this.runtime);
|
||||
|
||||
this.blockListener = this.blockListener.bind(this);
|
||||
this.flyoutBlockListener = this.flyoutBlockListener.bind(this);
|
||||
|
|
Loading…
Reference in a new issue