Merge pull request #4940 from picklesrus/revert-4790
Revert "Merge pull request #4790 from LLK/e16n"
This commit is contained in:
5 files changed
+21
-382
No files matched your search
+1
-1
@@ -111,7 +111,7 @@
|
||||
"scratch-render": "0.1.0-prerelease.20190605151415",
|
||||
"scratch-storage": "1.3.2",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20190523193400",
|
||||
"scratch-vm": "0.2.0-prerelease.20190619042313",
|
||||
"scratch-vm": "0.2.0-prerelease.20190610152034",
|
||||
"selenium-webdriver": "3.6.0",
|
||||
"startaudiocontext": "1.2.1",
|
||||
"style-loader": "^0.23.0",
|
||||
|
||||
@@ -17,7 +17,6 @@ import errorBoundaryHOC from '../lib/error-boundary-hoc.jsx';
|
||||
import {STAGE_DISPLAY_SIZES} from '../lib/layout-constants';
|
||||
import DropAreaHOC from '../lib/drop-area-hoc.jsx';
|
||||
import DragConstants from '../lib/drag-constants';
|
||||
import defineDynamicBlock from '../lib/define-dynamic-block';
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {updateToolbox} from '../reducers/toolbox';
|
||||
@@ -394,50 +393,20 @@ class Blocks extends React.Component {
|
||||
// workspace to be 'undone' here.
|
||||
this.workspace.clearUndo();
|
||||
}
|
||||
handleExtensionAdded (categoryInfo) {
|
||||
const defineBlocks = blockInfoArray => {
|
||||
if (blockInfoArray && blockInfoArray.length > 0) {
|
||||
const staticBlocksJson = [];
|
||||
const dynamicBlocksInfo = [];
|
||||
blockInfoArray.forEach(blockInfo => {
|
||||
if (blockInfo.info && blockInfo.info.isDynamic) {
|
||||
dynamicBlocksInfo.push(blockInfo);
|
||||
} else if (blockInfo.json) {
|
||||
staticBlocksJson.push(blockInfo.json);
|
||||
}
|
||||
// otherwise it's a non-block entry such as '---'
|
||||
});
|
||||
handleExtensionAdded (blocksInfo) {
|
||||
// select JSON from each block info object then reject the pseudo-blocks which don't have JSON, like separators
|
||||
// this actually defines blocks and MUST run regardless of the UI state
|
||||
this.ScratchBlocks.defineBlocksWithJsonArray(blocksInfo.map(blockInfo => blockInfo.json).filter(x => x));
|
||||
|
||||
this.ScratchBlocks.defineBlocksWithJsonArray(staticBlocksJson);
|
||||
dynamicBlocksInfo.forEach(blockInfo => {
|
||||
// This is creating the block factory / constructor -- NOT a specific instance of the block.
|
||||
// The factory should only know static info about the block: the category info and the opcode.
|
||||
// Anything else will be picked up from the XML attached to the block instance.
|
||||
const extendedOpcode = `${categoryInfo.id}_${blockInfo.info.opcode}`;
|
||||
const blockDefinition =
|
||||
defineDynamicBlock(this.ScratchBlocks, categoryInfo, blockInfo, extendedOpcode);
|
||||
this.ScratchBlocks.Blocks[extendedOpcode] = blockDefinition;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// scratch-blocks implements a menu or custom field as a special kind of block ("shadow" block)
|
||||
// these actually define blocks and MUST run regardless of the UI state
|
||||
defineBlocks(
|
||||
Object.getOwnPropertyNames(categoryInfo.customFieldTypes)
|
||||
.map(fieldTypeName => categoryInfo.customFieldTypes[fieldTypeName].scratchBlocksDefinition));
|
||||
defineBlocks(categoryInfo.menus);
|
||||
defineBlocks(categoryInfo.blocks);
|
||||
|
||||
// Update the toolbox with new blocks if possible
|
||||
// Update the toolbox with new blocks
|
||||
const toolboxXML = this.getToolboxXML();
|
||||
if (toolboxXML) {
|
||||
this.props.updateToolboxState(toolboxXML);
|
||||
}
|
||||
}
|
||||
handleBlocksInfoUpdate (categoryInfo) {
|
||||
handleBlocksInfoUpdate (blocksInfo) {
|
||||
// @todo Later we should replace this to avoid all the warnings from redefining blocks.
|
||||
this.handleExtensionAdded(categoryInfo);
|
||||
this.handleExtensionAdded(blocksInfo);
|
||||
}
|
||||
handleCategorySelected (categoryId) {
|
||||
const extension = extensionData.find(ext => ext.extensionId === categoryId);
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
// TODO: access `BlockType` and `ArgumentType` without reaching into VM
|
||||
// Should we move these into a new extension support module or something?
|
||||
import ArgumentType from 'scratch-vm/src/extension-support/argument-type';
|
||||
import BlockType from 'scratch-vm/src/extension-support/block-type';
|
||||
|
||||
/**
|
||||
* Define a block using extension info which has the ability to dynamically determine (and update) its layout.
|
||||
* This functionality is used for extension blocks which can change its properties based on different state
|
||||
* information. For example, the `control_stop` block changes its shape based on which menu item is selected
|
||||
* and a variable block changes its text to reflect the variable name without using an editable field.
|
||||
* @param {object} ScratchBlocks - The ScratchBlocks name space.
|
||||
* @param {object} categoryInfo - Information about this block's extension category, including any menus and icons.
|
||||
* @param {object} staticBlockInfo - The base block information before any dynamic changes.
|
||||
* @param {string} extendedOpcode - The opcode for the block (including the extension ID).
|
||||
*/
|
||||
// TODO: grow this until it can fully replace `_convertForScratchBlocks` in the VM runtime
|
||||
const defineDynamicBlock = (ScratchBlocks, categoryInfo, staticBlockInfo, extendedOpcode) => ({
|
||||
init: function () {
|
||||
const blockJson = {
|
||||
type: extendedOpcode,
|
||||
inputsInline: true,
|
||||
category: categoryInfo.name,
|
||||
colour: categoryInfo.color1,
|
||||
colourSecondary: categoryInfo.color2,
|
||||
colourTertiary: categoryInfo.color3
|
||||
};
|
||||
// There is a scratch-blocks / Blockly extension called "scratch_extension" which adjusts the styling of
|
||||
// blocks to allow for an icon, a feature of Scratch extension blocks. However, Scratch "core" extension
|
||||
// blocks don't have icons and so they should not use 'scratch_extension'. Adding a scratch-blocks / Blockly
|
||||
// extension after `jsonInit` isn't fully supported (?), so we decide now whether there will be an icon.
|
||||
if (staticBlockInfo.blockIconURI || categoryInfo.blockIconURI) {
|
||||
blockJson.extensions = ['scratch_extension'];
|
||||
}
|
||||
// initialize the basics of the block, to be overridden & extended later by `domToMutation`
|
||||
this.jsonInit(blockJson);
|
||||
// initialize the cached block info used to carry block info from `domToMutation` to `mutationToDom`
|
||||
this.blockInfoText = '{}';
|
||||
// we need a block info update (through `domToMutation`) before we have a completely initialized block
|
||||
this.needsBlockInfoUpdate = true;
|
||||
},
|
||||
mutationToDom: function () {
|
||||
const container = document.createElement('mutation');
|
||||
container.setAttribute('blockInfo', this.blockInfoText);
|
||||
return container;
|
||||
},
|
||||
domToMutation: function (xmlElement) {
|
||||
const blockInfoText = xmlElement.getAttribute('blockInfo');
|
||||
if (!blockInfoText) return;
|
||||
if (!this.needsBlockInfoUpdate) {
|
||||
throw new Error('Attempted to update block info twice');
|
||||
}
|
||||
delete this.needsBlockInfoUpdate;
|
||||
this.blockInfoText = blockInfoText;
|
||||
const blockInfo = JSON.parse(blockInfoText);
|
||||
|
||||
switch (blockInfo.blockType) {
|
||||
case BlockType.COMMAND:
|
||||
case BlockType.CONDITIONAL:
|
||||
case BlockType.LOOP:
|
||||
this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_SQUARE);
|
||||
this.setPreviousStatement(true);
|
||||
this.setNextStatement(!blockInfo.isTerminal);
|
||||
break;
|
||||
case BlockType.REPORTER:
|
||||
this.setOutput(true);
|
||||
this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_ROUND);
|
||||
if (!blockInfo.disableMonitor) {
|
||||
this.setCheckboxInFlyout(true);
|
||||
}
|
||||
break;
|
||||
case BlockType.BOOLEAN:
|
||||
this.setOutput(true);
|
||||
this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_HEXAGONAL);
|
||||
break;
|
||||
case BlockType.HAT:
|
||||
case BlockType.EVENT:
|
||||
this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_SQUARE);
|
||||
this.setNextStatement(true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (blockInfo.color1 || blockInfo.color2 || blockInfo.color3) {
|
||||
// `setColour` handles undefined parameters by adjusting defined colors
|
||||
this.setColour(blockInfo.color1, blockInfo.color2, blockInfo.color3);
|
||||
}
|
||||
|
||||
// Layout block arguments
|
||||
// TODO handle E/C Blocks
|
||||
const blockText = blockInfo.text;
|
||||
const args = [];
|
||||
let argCount = 0;
|
||||
const scratchBlocksStyleText = blockText.replace(/\[(.+?)]/g, (match, argName) => {
|
||||
const arg = blockInfo.arguments[argName];
|
||||
switch (arg.type) {
|
||||
case ArgumentType.STRING:
|
||||
args.push({type: 'input_value', name: argName});
|
||||
break;
|
||||
case ArgumentType.BOOLEAN:
|
||||
args.push({type: 'input_value', name: argName, check: 'Boolean'});
|
||||
break;
|
||||
}
|
||||
return `%${++argCount}`;
|
||||
});
|
||||
this.interpolate_(scratchBlocksStyleText, args);
|
||||
}
|
||||
});
|
||||
|
||||
export default defineDynamicBlock;
|
||||
+13
-36
@@ -716,16 +716,13 @@ const xmlClose = '</xml>';
|
||||
/**
|
||||
* @param {!boolean} isStage - Whether the toolbox is for a stage-type target.
|
||||
* @param {?string} targetId - The current editing target
|
||||
* @param {?Array.<object>} categoriesXML - optional array of `{id,xml}` for categories. This can include both core
|
||||
* and other extensions: core extensions will be placed in the normal Scratch order; others will go at the bottom.
|
||||
* @property {string} id - the extension / category ID.
|
||||
* @property {string} xml - the `<category>...</category>` XML for this extension / category.
|
||||
* @param {?string} categoriesXML - null for default toolbox, or an XML string with <category> elements.
|
||||
* @param {?string} costumeName - The name of the default selected costume dropdown.
|
||||
* @param {?string} backdropName - The name of the default selected backdrop dropdown.
|
||||
* @param {?string} soundName - The name of the default selected sound dropdown.
|
||||
* @returns {string} - a ScratchBlocks-style XML document for the contents of the toolbox.
|
||||
*/
|
||||
const makeToolboxXML = function (isStage, targetId, categoriesXML = [],
|
||||
const makeToolboxXML = function (isStage, targetId, categoriesXML,
|
||||
costumeName = '', backdropName = '', soundName = '') {
|
||||
const gap = [categorySeparator];
|
||||
|
||||
@@ -733,41 +730,21 @@ const makeToolboxXML = function (isStage, targetId, categoriesXML = [],
|
||||
backdropName = xmlEscape(backdropName);
|
||||
soundName = xmlEscape(soundName);
|
||||
|
||||
categoriesXML = categoriesXML.slice();
|
||||
const moveCategory = categoryId => {
|
||||
const index = categoriesXML.findIndex(categoryInfo => categoryInfo.id === categoryId);
|
||||
if (index >= 0) {
|
||||
// remove the category from categoriesXML and return its XML
|
||||
const [categoryInfo] = categoriesXML.splice(index, 1);
|
||||
return categoryInfo.xml;
|
||||
}
|
||||
// return `undefined`
|
||||
};
|
||||
const motionXML = moveCategory('motion') || motion(isStage, targetId);
|
||||
const looksXML = moveCategory('looks') || looks(isStage, targetId, costumeName, backdropName);
|
||||
const soundXML = moveCategory('sound') || sound(isStage, targetId, soundName);
|
||||
const eventsXML = moveCategory('event') || events(isStage, targetId);
|
||||
const controlXML = moveCategory('control') || control(isStage, targetId);
|
||||
const sensingXML = moveCategory('sensing') || sensing(isStage, targetId);
|
||||
const operatorsXML = moveCategory('operators') || operators(isStage, targetId);
|
||||
const variablesXML = moveCategory('data') || variables(isStage, targetId);
|
||||
const myBlocksXML = moveCategory('procedures') || myBlocks(isStage, targetId);
|
||||
|
||||
const everything = [
|
||||
xmlOpen,
|
||||
motionXML, gap,
|
||||
looksXML, gap,
|
||||
soundXML, gap,
|
||||
eventsXML, gap,
|
||||
controlXML, gap,
|
||||
sensingXML, gap,
|
||||
operatorsXML, gap,
|
||||
variablesXML, gap,
|
||||
myBlocksXML
|
||||
motion(isStage, targetId), gap,
|
||||
looks(isStage, targetId, costumeName, backdropName), gap,
|
||||
sound(isStage, targetId, soundName), gap,
|
||||
events(isStage, targetId), gap,
|
||||
control(isStage, targetId), gap,
|
||||
sensing(isStage, targetId), gap,
|
||||
operators(isStage, targetId), gap,
|
||||
variables(isStage, targetId), gap,
|
||||
myBlocks(isStage, targetId)
|
||||
];
|
||||
|
||||
for (const extensionCategory of categoriesXML) {
|
||||
everything.push(gap, extensionCategory.xml);
|
||||
if (categoriesXML) {
|
||||
everything.push(gap, categoriesXML);
|
||||
}
|
||||
|
||||
everything.push(xmlClose);
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
import defineDynamicBlock from '../../../src/lib/define-dynamic-block';
|
||||
|
||||
import BlockType from 'scratch-vm/src/extension-support/block-type';
|
||||
|
||||
const MockScratchBlocks = {
|
||||
OUTPUT_SHAPE_HEXAGONAL: 1,
|
||||
OUTPUT_SHAPE_ROUND: 2,
|
||||
OUTPUT_SHAPE_SQUARE: 3
|
||||
};
|
||||
|
||||
const categoryInfo = {
|
||||
name: 'test category',
|
||||
color1: '#111',
|
||||
color2: '#222',
|
||||
color3: '#333'
|
||||
};
|
||||
|
||||
const penIconURI = 'data:image/svg+xml;base64,fake_pen_icon_svg_base64_data';
|
||||
|
||||
const testBlockInfo = {
|
||||
commandWithIcon: {
|
||||
blockType: BlockType.COMMAND,
|
||||
blockIconURI: penIconURI,
|
||||
text: 'command with icon'
|
||||
},
|
||||
commandWithoutIcon: {
|
||||
blockType: BlockType.COMMAND,
|
||||
text: 'command without icon'
|
||||
},
|
||||
terminalCommand: {
|
||||
blockType: BlockType.COMMAND,
|
||||
isTerminal: true,
|
||||
text: 'terminal command'
|
||||
},
|
||||
reporter: {
|
||||
blockType: BlockType.REPORTER,
|
||||
text: 'reporter'
|
||||
},
|
||||
boolean: {
|
||||
blockType: BlockType.BOOLEAN,
|
||||
text: 'Boolean'
|
||||
},
|
||||
hat: {
|
||||
blockType: BlockType.HAT,
|
||||
text: 'hat'
|
||||
}
|
||||
};
|
||||
|
||||
// similar to goog.mixin from the Closure library
|
||||
const mixin = function (target, source) {
|
||||
for (const x in source) {
|
||||
target[x] = source[x];
|
||||
}
|
||||
};
|
||||
|
||||
class MockBlock {
|
||||
constructor (blockInfo, extendedOpcode) {
|
||||
// mimic Closure-style inheritance by mixing in `defineDynamicBlock` output as this instance's prototype
|
||||
// see also the `Blockly.Block` constructor
|
||||
const prototype = defineDynamicBlock(MockScratchBlocks, categoryInfo, blockInfo, extendedOpcode);
|
||||
mixin(this, prototype);
|
||||
this.init();
|
||||
|
||||
// bootstrap the mutation<->DOM cycle
|
||||
this.blockInfoText = JSON.stringify(blockInfo);
|
||||
const xmlElement = this.mutationToDom();
|
||||
|
||||
// parse blockInfo from XML to fill dynamic properties
|
||||
this.domToMutation(xmlElement);
|
||||
}
|
||||
|
||||
jsonInit (json) {
|
||||
this.result = Object.assign({}, json);
|
||||
}
|
||||
interpolate_ () {
|
||||
// TODO: add tests for this?
|
||||
}
|
||||
setCheckboxInFlyout (isEnabled) {
|
||||
this.result.checkboxInFlyout_ = isEnabled;
|
||||
}
|
||||
setOutput (isEnabled) {
|
||||
this.result.outputConnection = isEnabled; // Blockly calls `makeConnection_` here
|
||||
}
|
||||
setOutputShape (outputShape) {
|
||||
this.result.outputShape_ = outputShape;
|
||||
}
|
||||
setNextStatement (isEnabled) {
|
||||
this.result.nextConnection = isEnabled; // Blockly calls `makeConnection_` here
|
||||
}
|
||||
setPreviousStatement (isEnabled) {
|
||||
this.result.previousConnection = isEnabled; // Blockly calls `makeConnection_` here
|
||||
}
|
||||
}
|
||||
|
||||
describe('defineDynamicBlock', () => {
|
||||
test('is a function', () => {
|
||||
expect(typeof defineDynamicBlock).toBe('function');
|
||||
});
|
||||
test('can define a command block with an icon', () => {
|
||||
const extendedOpcode = 'test.commandWithIcon';
|
||||
const block = new MockBlock(testBlockInfo.commandWithIcon, extendedOpcode);
|
||||
expect(block.result).toEqual({
|
||||
category: categoryInfo.name,
|
||||
colour: categoryInfo.color1,
|
||||
colourSecondary: categoryInfo.color2,
|
||||
colourTertiary: categoryInfo.color3,
|
||||
extensions: ['scratch_extension'],
|
||||
inputsInline: true,
|
||||
nextConnection: true,
|
||||
outputShape_: MockScratchBlocks.OUTPUT_SHAPE_SQUARE,
|
||||
previousConnection: true,
|
||||
type: extendedOpcode
|
||||
});
|
||||
});
|
||||
test('can define a command block without an icon', () => {
|
||||
const extendedOpcode = 'test.commandWithoutIcon';
|
||||
const block = new MockBlock(testBlockInfo.commandWithoutIcon, extendedOpcode);
|
||||
expect(block.result).toEqual({
|
||||
category: categoryInfo.name,
|
||||
colour: categoryInfo.color1,
|
||||
colourSecondary: categoryInfo.color2,
|
||||
colourTertiary: categoryInfo.color3,
|
||||
// extensions: undefined, // no icon means no extension
|
||||
inputsInline: true,
|
||||
nextConnection: true,
|
||||
outputShape_: MockScratchBlocks.OUTPUT_SHAPE_SQUARE,
|
||||
previousConnection: true,
|
||||
type: extendedOpcode
|
||||
});
|
||||
});
|
||||
test('can define a terminal command', () => {
|
||||
const extendedOpcode = 'test.terminal';
|
||||
const block = new MockBlock(testBlockInfo.terminalCommand, extendedOpcode);
|
||||
expect(block.result).toEqual({
|
||||
category: categoryInfo.name,
|
||||
colour: categoryInfo.color1,
|
||||
colourSecondary: categoryInfo.color2,
|
||||
colourTertiary: categoryInfo.color3,
|
||||
// extensions: undefined, // no icon means no extension
|
||||
inputsInline: true,
|
||||
nextConnection: false, // terminal
|
||||
outputShape_: MockScratchBlocks.OUTPUT_SHAPE_SQUARE,
|
||||
previousConnection: true,
|
||||
type: extendedOpcode
|
||||
});
|
||||
});
|
||||
test('can define a reporter', () => {
|
||||
const extendedOpcode = 'test.reporter';
|
||||
const block = new MockBlock(testBlockInfo.reporter, extendedOpcode);
|
||||
expect(block.result).toEqual({
|
||||
category: categoryInfo.name,
|
||||
checkboxInFlyout_: true,
|
||||
colour: categoryInfo.color1,
|
||||
colourSecondary: categoryInfo.color2,
|
||||
colourTertiary: categoryInfo.color3,
|
||||
// extensions: undefined, // no icon means no extension
|
||||
inputsInline: true,
|
||||
// nextConnection: undefined, // reporter
|
||||
outputConnection: true, // reporter
|
||||
outputShape_: MockScratchBlocks.OUTPUT_SHAPE_ROUND, // reporter
|
||||
// previousConnection: undefined, // reporter
|
||||
type: extendedOpcode
|
||||
});
|
||||
});
|
||||
test('can define a Boolean', () => {
|
||||
const extendedOpcode = 'test.boolean';
|
||||
const block = new MockBlock(testBlockInfo.boolean, extendedOpcode);
|
||||
expect(block.result).toEqual({
|
||||
category: categoryInfo.name,
|
||||
// checkboxInFlyout_: undefined,
|
||||
colour: categoryInfo.color1,
|
||||
colourSecondary: categoryInfo.color2,
|
||||
colourTertiary: categoryInfo.color3,
|
||||
// extensions: undefined, // no icon means no extension
|
||||
inputsInline: true,
|
||||
// nextConnection: undefined, // reporter
|
||||
outputConnection: true, // reporter
|
||||
outputShape_: MockScratchBlocks.OUTPUT_SHAPE_HEXAGONAL, // Boolean
|
||||
// previousConnection: undefined, // reporter
|
||||
type: extendedOpcode
|
||||
});
|
||||
});
|
||||
test('can define a hat', () => {
|
||||
const extendedOpcode = 'test.hat';
|
||||
const block = new MockBlock(testBlockInfo.hat, extendedOpcode);
|
||||
expect(block.result).toEqual({
|
||||
category: categoryInfo.name,
|
||||
colour: categoryInfo.color1,
|
||||
colourSecondary: categoryInfo.color2,
|
||||
colourTertiary: categoryInfo.color3,
|
||||
// extensions: undefined, // no icon means no extension
|
||||
inputsInline: true,
|
||||
nextConnection: true,
|
||||
outputShape_: MockScratchBlocks.OUTPUT_SHAPE_SQUARE,
|
||||
// previousConnection: undefined, // hat
|
||||
type: extendedOpcode
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user