mirror of
https://github.com/google/blockly.git
synced 2026-01-08 01:20:12 +01:00
BlockFactory: Adding JSON/JavaScript import support (#1235)
Updates the BlockFactory main workspace based on the preview block generated from manual construction. This is a continuation of PR #1216 by @JC-Orozco, rebased on latest develop branch. Bring it latest code up to Google styling and standards.
This commit is contained in:
committed by
GitHub
parent
29582ba0d7
commit
1956baa963
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,4 +5,4 @@ npm-debug.log
|
|||||||
.project
|
.project
|
||||||
*.pyc
|
*.pyc
|
||||||
*.komodoproject
|
*.komodoproject
|
||||||
/nbproject/private/
|
/nbproject/private/
|
||||||
|
|||||||
@@ -584,9 +584,9 @@ AppController.prototype.addBlockFactoryEventListeners = function() {
|
|||||||
document.getElementById('direction')
|
document.getElementById('direction')
|
||||||
.addEventListener('change', BlockFactory.updatePreview);
|
.addEventListener('change', BlockFactory.updatePreview);
|
||||||
document.getElementById('languageTA')
|
document.getElementById('languageTA')
|
||||||
.addEventListener('change', BlockFactory.updatePreview);
|
.addEventListener('change', BlockFactory.manualEdit);
|
||||||
document.getElementById('languageTA')
|
document.getElementById('languageTA')
|
||||||
.addEventListener('keyup', BlockFactory.updatePreview);
|
.addEventListener('keyup', BlockFactory.manualEdit);
|
||||||
document.getElementById('format')
|
document.getElementById('format')
|
||||||
.addEventListener('change', BlockFactory.formatChange);
|
.addEventListener('change', BlockFactory.formatChange);
|
||||||
document.getElementById('language')
|
document.getElementById('language')
|
||||||
|
|||||||
749
demos/blockfactory/block_definition_extractor.js
Normal file
749
demos/blockfactory/block_definition_extractor.js
Normal file
@@ -0,0 +1,749 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2017 Juan Carlos Orozco Arena
|
||||||
|
* Apache License Version 2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @fileoverview
|
||||||
|
* The BlockDefinitionExtractor is a class that generates a workspace DOM
|
||||||
|
* suitable for the BlockFactory's block editor, derived from an example
|
||||||
|
* Blockly.Block.
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* var workspaceDom = new BlockDefinitionExtractor()
|
||||||
|
* .buildBlockFactoryWorkspace(exampleBlocklyBlock);
|
||||||
|
* Blockly.Xml.domToWorkspace(workspaceDom, BlockFactory.mainWorkspace);
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* The <code>exampleBlocklyBlock</code> is usually the block loaded into the
|
||||||
|
* preview workspace after manually entering the block definition.
|
||||||
|
*
|
||||||
|
* @author JC-Orozco (Juan Carlos Orozco), AnmAtAnm (Andrew n marshall)
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Namespace for BlockDefinitionExtractor.
|
||||||
|
*/
|
||||||
|
goog.provide('BlockDefinitionExtractor');
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to contain all functions needed to extract block definition from
|
||||||
|
* the block preview data structure.
|
||||||
|
* @namespace
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor = BlockDefinitionExtractor || Object.create(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a BlockFactory workspace that reflects the block structure of the
|
||||||
|
* exmaple block.
|
||||||
|
*
|
||||||
|
* @param {!Blockly.Block} block The reference block from which the definition
|
||||||
|
* will be extracted.
|
||||||
|
* @return {!Element} Returns the root workspace DOM <xml> for the block editor
|
||||||
|
* workspace.
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.buildBlockFactoryWorkspace = function(block) {
|
||||||
|
var workspaceXml = goog.dom.createDom('xml');
|
||||||
|
workspaceXml.append(
|
||||||
|
BlockDefinitionExtractor.factoryBase_(block, block.type));
|
||||||
|
|
||||||
|
return workspaceXml;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to create a new Element with the provided attributes and
|
||||||
|
* inner text.
|
||||||
|
*
|
||||||
|
* @param {string} name New element tag name.
|
||||||
|
* @param {Map<String,String>} opt_attrs Optional list of attributes.
|
||||||
|
* @param {string?} opt_text Optional inner text.
|
||||||
|
* @return {!Element} The newly created element.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.newDomElement_ = function(name, opt_attrs, opt_text) {
|
||||||
|
// Avoid createDom(..)'s attributes argument for being too HTML specific.
|
||||||
|
var elem = goog.dom.createDom(name);
|
||||||
|
if (opt_attrs) {
|
||||||
|
for (var key in opt_attrs) {
|
||||||
|
elem.setAttribute(key, opt_attrs[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (opt_text) {
|
||||||
|
elem.append(opt_text);
|
||||||
|
}
|
||||||
|
return elem;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an connection type constraint <block> Element representing the
|
||||||
|
* requested type.
|
||||||
|
*
|
||||||
|
* @param {string} type Type name of desired connection constraint.
|
||||||
|
* @return {!Element} The <block> representing the the constraint type.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.buildBlockForType_ = function(type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'Null':
|
||||||
|
return BlockDefinitionExtractor.typeNull_();
|
||||||
|
case 'Boolean':
|
||||||
|
return BlockDefinitionExtractor.typeBoolean_();
|
||||||
|
case 'Number':
|
||||||
|
return BlockDefinitionExtractor.typeNumber_();
|
||||||
|
case 'String':
|
||||||
|
return BlockDefinitionExtractor.typeString_();
|
||||||
|
case 'Array':
|
||||||
|
return BlockDefinitionExtractor.typeList_();
|
||||||
|
default:
|
||||||
|
return BlockDefinitionExtractor.typeOther_(type);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a <block> element representing the type constraints of the
|
||||||
|
* provided connection.
|
||||||
|
*
|
||||||
|
* @param {!Blockly.Connection} connection The connection with desired
|
||||||
|
* connection constraints.
|
||||||
|
* @return {!Element} The root <block> element of the constraint definition.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.buildTypeConstraintBlockForConnection_ =
|
||||||
|
function(connection)
|
||||||
|
{
|
||||||
|
var typeBlock;
|
||||||
|
if (connection.check_) {
|
||||||
|
if (connection.check_.length < 1) {
|
||||||
|
typeBlock = BlockDefinitionExtractor.typeNullShadow_();
|
||||||
|
} else if (connection.check_.length === 1) {
|
||||||
|
typeBlock = BlockDefinitionExtractor.buildBlockForType_(
|
||||||
|
connection.check_[0]);
|
||||||
|
} else if (connection.check_.length > 1 ) {
|
||||||
|
typeBlock = BlockDefinitionExtractor.typeGroup_(connection.check_);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
typeBlock = BlockDefinitionExtractor.typeNullShadow_();
|
||||||
|
}
|
||||||
|
return typeBlock;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the root "factory_base" <block> element for the block definition.
|
||||||
|
*
|
||||||
|
* @param {!Blockly.Block} block The example block from which to extract the
|
||||||
|
* definition.
|
||||||
|
* @param {string} name Block name.
|
||||||
|
* @return {!Element} The factory_base block element.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.factoryBase_ = function(block, name) {
|
||||||
|
BlockDefinitionExtractor.src = {root: block, current: block};
|
||||||
|
var factoryBaseEl =
|
||||||
|
BlockDefinitionExtractor.newDomElement_('block', {type: 'factory_base'});
|
||||||
|
factoryBaseEl.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'NAME'}, name));
|
||||||
|
factoryBaseEl.append(BlockDefinitionExtractor.buildInlineField_(block));
|
||||||
|
|
||||||
|
BlockDefinitionExtractor.buildConnections_(block, factoryBaseEl);
|
||||||
|
|
||||||
|
var inputsStatement = BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'statement', {name: 'INPUTS'});
|
||||||
|
inputsStatement.append(BlockDefinitionExtractor.parseInputs_(block));
|
||||||
|
factoryBaseEl.append(inputsStatement);
|
||||||
|
|
||||||
|
var tooltipValue =
|
||||||
|
BlockDefinitionExtractor.newDomElement_('value', {name: 'TOOLTIP'});
|
||||||
|
tooltipValue.append(BlockDefinitionExtractor.text_(block.tooltip));
|
||||||
|
factoryBaseEl.append(tooltipValue);
|
||||||
|
|
||||||
|
var helpUrlValue =
|
||||||
|
BlockDefinitionExtractor.newDomElement_('value', {name: 'HELPURL'});
|
||||||
|
helpUrlValue.append(BlockDefinitionExtractor.text_(block.helpUrl));
|
||||||
|
factoryBaseEl.append(helpUrlValue);
|
||||||
|
|
||||||
|
// Convert colour_ to hue value 0-360 degrees
|
||||||
|
// TODO(#1247): Solve off-by-one errors.
|
||||||
|
// TODO: Deal with colors that don't map to standard hues. (Needs improved
|
||||||
|
// block definitions.)
|
||||||
|
var colour_hue = Math.floor(
|
||||||
|
goog.color.hexToHsv(block.colour_)[0]); // Off by one... sometimes
|
||||||
|
var colourBlock = BlockDefinitionExtractor.colourBlockFromHue_(colour_hue);
|
||||||
|
var colourInputValue =
|
||||||
|
BlockDefinitionExtractor.newDomElement_('value', {name: 'COLOUR'});
|
||||||
|
colourInputValue.append(colourBlock);
|
||||||
|
factoryBaseEl.append(colourInputValue);
|
||||||
|
return factoryBaseEl;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the appropriate <field> element for the block definition's
|
||||||
|
* CONNECTIONS field, which determines the next, previous, and output
|
||||||
|
* connections.
|
||||||
|
*
|
||||||
|
* @param {!Blockly.Block} block The example block from which to extract the
|
||||||
|
* definition.
|
||||||
|
* @param {!Element} factoryBaseEl The root of the block definition.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.buildConnections_ = function(block, factoryBaseEl) {
|
||||||
|
var connections = 'NONE';
|
||||||
|
if (block.outputConnection) {
|
||||||
|
connections = 'LEFT';
|
||||||
|
} else {
|
||||||
|
if (block.previousConnection) {
|
||||||
|
if (block.nextConnection) {
|
||||||
|
connections = 'BOTH';
|
||||||
|
} else {
|
||||||
|
connections = 'TOP';
|
||||||
|
}
|
||||||
|
} else if (block.nextConnection) {
|
||||||
|
connections = 'BOTTOM';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
factoryBaseEl.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'CONNECTIONS'}, connections));
|
||||||
|
|
||||||
|
if (connections === 'LEFT') {
|
||||||
|
var inputValue =
|
||||||
|
BlockDefinitionExtractor.newDomElement_('value', {name: 'OUTPUTTYPE'});
|
||||||
|
inputValue.append(
|
||||||
|
BlockDefinitionExtractor.buildTypeConstraintBlockForConnection_(
|
||||||
|
block.outputConnection));
|
||||||
|
factoryBaseEl.append(inputValue);
|
||||||
|
} else {
|
||||||
|
if (connections === 'UP' || connections === 'BOTH') {
|
||||||
|
var inputValue =
|
||||||
|
BlockDefinitionExtractor.newDomElement_('value', {name: 'TOPTYPE'});
|
||||||
|
inputValue.append(
|
||||||
|
BlockDefinitionExtractor.buildTypeConstraintBlockForConnection_(
|
||||||
|
block.previousConnection));
|
||||||
|
factoryBaseEl.append(inputValue);
|
||||||
|
}
|
||||||
|
if (connections === 'DOWN' || connections === 'BOTH') {
|
||||||
|
var inputValue = BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'value', {name: 'BOTTOMTYPE'});
|
||||||
|
inputValue.append(
|
||||||
|
BlockDefinitionExtractor.buildTypeConstraintBlockForConnection_(
|
||||||
|
block.nextConnection));
|
||||||
|
factoryBaseEl.append(inputValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the appropriate <field> element for the block definition's INLINE
|
||||||
|
* field.
|
||||||
|
*
|
||||||
|
* @param {!Blockly.Block} block The example block from which to extract the
|
||||||
|
* definition.
|
||||||
|
* @return {Element} The INLINE <field> with value 'AUTO', 'INT' (internal) or
|
||||||
|
* 'EXT' (external).
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.buildInlineField_ = function(block) {
|
||||||
|
var inline = 'AUTO'; // When block.inputsInlineDefault === undefined
|
||||||
|
if (block.inputsInlineDefault === true) {
|
||||||
|
inline = 'INT';
|
||||||
|
} else if (block.inputsInlineDefault === false) {
|
||||||
|
inline = 'EXT';
|
||||||
|
}
|
||||||
|
return BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'INLINE'}, inline);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a sequence of <block> elements that represent the inputs of the
|
||||||
|
* provided block.
|
||||||
|
*
|
||||||
|
* @param {!Blockly.Block} block The source block to copy the inputs of.
|
||||||
|
* @return {Element} The fist <block> element of the sequence
|
||||||
|
* (and the root of the constructed DOM).
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.parseInputs_ = function(block) {
|
||||||
|
var firstInputDefElement = null;
|
||||||
|
var lastInputDefElement = null;
|
||||||
|
for (var i = 0; i < block.inputList.length; i++) {
|
||||||
|
var input = block.inputList[i];
|
||||||
|
var align = 'LEFT'; // Left alignment is the default.
|
||||||
|
if (input.align === Blockly.ALIGN_CENTRE) {
|
||||||
|
align = 'CENTRE';
|
||||||
|
} else if (input.align === Blockly.ALIGN_RIGHT) {
|
||||||
|
align = 'RIGHT';
|
||||||
|
}
|
||||||
|
|
||||||
|
var inputDefElement = BlockDefinitionExtractor.input_(input, align);
|
||||||
|
if (lastInputDefElement) {
|
||||||
|
var next = BlockDefinitionExtractor.newDomElement_('next');
|
||||||
|
next.append(inputDefElement);
|
||||||
|
lastInputDefElement.append(next);
|
||||||
|
} else {
|
||||||
|
firstInputDefElement = inputDefElement;
|
||||||
|
}
|
||||||
|
lastInputDefElement = inputDefElement;
|
||||||
|
}
|
||||||
|
return firstInputDefElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a <block> element representing a block input.
|
||||||
|
*
|
||||||
|
* @param {!Blockly.Input} input The input object.
|
||||||
|
* @param {string} align Can be left, right or centre.
|
||||||
|
* @return {!Element} The <block> element that defines the input.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.input_ = function(input, align) {
|
||||||
|
var isDummy = (input.type === Blockly.DUMMY_INPUT);
|
||||||
|
var inputTypeAttr =
|
||||||
|
isDummy ? 'input_dummy' :
|
||||||
|
(input.type === Blockly.INPUT_VALUE) ? 'input_value' : 'input_statement';
|
||||||
|
var inputDefBlock =
|
||||||
|
BlockDefinitionExtractor.newDomElement_('block', {type: inputTypeAttr});
|
||||||
|
|
||||||
|
if (!isDummy) {
|
||||||
|
inputDefBlock.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'INPUTNAME'}, input.name));
|
||||||
|
}
|
||||||
|
inputDefBlock.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'ALIGN'}, align));
|
||||||
|
|
||||||
|
var fieldsDef = BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'statement', {name: 'FIELDS'});
|
||||||
|
var fieldsXml = BlockDefinitionExtractor.buildFields_(input.fieldRow);
|
||||||
|
fieldsDef.append(fieldsXml);
|
||||||
|
inputDefBlock.append(fieldsDef);
|
||||||
|
|
||||||
|
if (!isDummy) {
|
||||||
|
var typeValue = BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'value', {name: 'TYPE'});
|
||||||
|
typeValue.append(
|
||||||
|
BlockDefinitionExtractor.buildTypeConstraintBlockForConnection_(
|
||||||
|
input.connection));
|
||||||
|
inputDefBlock.append(typeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return inputDefBlock;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a sequence <block> elements representing the field definition.
|
||||||
|
* @param {Array<Blockly.Field>} fieldRow A list of fields in a Blockly.Input.
|
||||||
|
* @return {Element} The fist <block> element of the sequence
|
||||||
|
* (and the root of the constructed DOM).
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.buildFields_ = function(fieldRow) {
|
||||||
|
var firstFieldDefElement = null;
|
||||||
|
var lastFieldDefElement = null;
|
||||||
|
|
||||||
|
for (var i = 0; i < fieldRow.length; i++) {
|
||||||
|
var field = fieldRow[i];
|
||||||
|
var fieldDefElement = BlockDefinitionExtractor.buildFieldElement_(field);
|
||||||
|
|
||||||
|
if (lastFieldDefElement) {
|
||||||
|
var next = BlockDefinitionExtractor.newDomElement_('next');
|
||||||
|
next.append(fieldDefElement);
|
||||||
|
lastFieldDefElement.append(next);
|
||||||
|
} else {
|
||||||
|
firstFieldDefElement = fieldDefElement;
|
||||||
|
}
|
||||||
|
lastFieldDefElement = fieldDefElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstFieldDefElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a <field> element that describes the provided Blockly.Field.
|
||||||
|
* @param {!Blockly.Field} field The field from which the definition is copied.
|
||||||
|
* @param {!Element} A <field> for the Field definition.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.buildFieldElement_ = function(field) {
|
||||||
|
if (field instanceof Blockly.FieldLabel) {
|
||||||
|
return BlockDefinitionExtractor.buildFieldLabel_(field.text_);
|
||||||
|
} else if (field instanceof Blockly.FieldTextInput) {
|
||||||
|
return BlockDefinitionExtractor.buildFieldInput_(field.name, field.text_);
|
||||||
|
} else if (field instanceof Blockly.FieldNumber) {
|
||||||
|
return BlockDefinitionExtractor.buildFieldNumber_(
|
||||||
|
field.name, field.text_, field.min_, field.max_, field.presicion_);
|
||||||
|
} else if (field instanceof Blockly.FieldAngle) {
|
||||||
|
return BlockDefinitionExtractor.buildFieldAngle_(field.name, field.text_);
|
||||||
|
} else if (field instanceof Blockly.FieldCheckbox) {
|
||||||
|
return BlockDefinitionExtractor.buildFieldCheckbox_(field.name, field.state_);
|
||||||
|
} else if (field instanceof Blockly.FieldColour) {
|
||||||
|
return BlockDefinitionExtractor.buildFieldColour_(field.name, field.colour_);
|
||||||
|
} else if (field instanceof Blockly.FieldImage) {
|
||||||
|
return BlockDefinitionExtractor.buildFieldImage_(
|
||||||
|
field.src_, field.width_, field.height_, field.text_);
|
||||||
|
} else if (field instanceof Blockly.FieldVariable) {
|
||||||
|
// FieldVariable must be before FieldDropdown, because FieldVariable is a
|
||||||
|
// subclass.
|
||||||
|
return BlockDefinitionExtractor.buildFieldVariable_(field.name, field.text_);
|
||||||
|
} else if (field instanceof Blockly.FieldDropdown) {
|
||||||
|
return BlockDefinitionExtractor.buildFieldDropdown_(field);
|
||||||
|
}
|
||||||
|
throw Error('Unrecognized field class: ' + field.constructor.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a <block> element representing a FieldLabel definition.
|
||||||
|
* @param {string} text
|
||||||
|
* @return {Element} The XML for FieldLabel definition.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.buildFieldLabel_ = function(text) {
|
||||||
|
var fieldBlock =
|
||||||
|
BlockDefinitionExtractor.newDomElement_('block', {type: 'field_static'});
|
||||||
|
fieldBlock.append(
|
||||||
|
BlockDefinitionExtractor.newDomElement_('field', {name: 'TEXT'}, text));
|
||||||
|
return fieldBlock;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a <block> element representing a FieldInput (text input) definition.
|
||||||
|
*
|
||||||
|
* @param {string} fieldName The identifying name of the field.
|
||||||
|
* @param {string} text The default text string.
|
||||||
|
* @return {Element} The XML for FieldInput definition.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.buildFieldInput_ = function(fieldName, text) {
|
||||||
|
var fieldInput =
|
||||||
|
BlockDefinitionExtractor.newDomElement_('block', {type: 'field_input'});
|
||||||
|
fieldInput.append(
|
||||||
|
BlockDefinitionExtractor.newDomElement_('field', {name: 'TEXT'}, text));
|
||||||
|
fieldInput.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'FIELDNAME'}, fieldName));
|
||||||
|
return fieldInput;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a <block> element representing a FieldNumber definition.
|
||||||
|
*
|
||||||
|
* @param {string} fieldName The identifying name of the field.
|
||||||
|
* @param {number} value The field's default value.
|
||||||
|
* @param {number} min The minimum allowed value, or negative infinity.
|
||||||
|
* @param {number} max The maximum allowed value, or positive infinity.
|
||||||
|
* @param {number} precision The precision allowed for the number.
|
||||||
|
* @return {Element} The XML for FieldNumber definition.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.buildFieldNumber_ =
|
||||||
|
function(fieldName, value, min, max, precision)
|
||||||
|
{
|
||||||
|
var fieldNumber =
|
||||||
|
BlockDefinitionExtractor.newDomElement_('block', {type: 'field_number'});
|
||||||
|
fieldNumber.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'VALUE'}, value));
|
||||||
|
fieldNumber.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'FIELDNAME'}, fieldName));
|
||||||
|
fieldNumber.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'MIN'}, min));
|
||||||
|
fieldNumber.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'MAX'}, max));
|
||||||
|
fieldNumber.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'PRECISION'}, precision));
|
||||||
|
return fieldNumber;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a <block> element representing a FieldAngle definition.
|
||||||
|
*
|
||||||
|
* @param {string} fieldName The identifying name of the field.
|
||||||
|
* @param {number} angle The field's default value.
|
||||||
|
* @return {Element} The XML for FieldAngle definition.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.buildFieldAngle_ = function(angle, fieldName) {
|
||||||
|
var fieldAngle =
|
||||||
|
BlockDefinitionExtractor.newDomElement_('block', {type: 'field_angle'});
|
||||||
|
fieldAngle.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'ANGLE'}, angle));
|
||||||
|
fieldAngle.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'FIELDNAME'}, fieldName));
|
||||||
|
return fieldAngle;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a <block> element representing a FieldDropdown definition.
|
||||||
|
*
|
||||||
|
* @param {Blockly.FieldDropdown} dropdown
|
||||||
|
* @return {Element} The <block> element representing a similar FieldDropdown.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.buildFieldDropdown_ = function(dropdown) {
|
||||||
|
var menuGenerator = dropdown.menuGenerator_;
|
||||||
|
if (typeof menuGenerator === 'function') {
|
||||||
|
var options = menuGenerator();
|
||||||
|
} else if (goog.isArray(menuGenerator)) {
|
||||||
|
var options = menuGenerator;
|
||||||
|
} else {
|
||||||
|
throw new Error('Unrecognized type of menuGenerator: ' + menuGenerator);
|
||||||
|
}
|
||||||
|
|
||||||
|
var fieldDropdown = BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'block', {type: 'field_dropdown'});
|
||||||
|
var optionsStr = '[';
|
||||||
|
|
||||||
|
var mutation = BlockDefinitionExtractor.newDomElement_('mutation');
|
||||||
|
fieldDropdown.append(mutation);
|
||||||
|
fieldDropdown.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'FIELDNAME'}, dropdown.name));
|
||||||
|
for (var i=0; i<options.length; i++) {
|
||||||
|
var option = options[i];
|
||||||
|
if (typeof option[0] === "string") {
|
||||||
|
optionsStr += '"text",'
|
||||||
|
fieldDropdown.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'USER'+i}, option[0]));
|
||||||
|
} else {
|
||||||
|
optionsStr += '"image",';
|
||||||
|
fieldDropdown.append(
|
||||||
|
BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'SRC'+i}, option[0].src));
|
||||||
|
fieldDropdown.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'WIDTH'+i}, option[0].width));
|
||||||
|
fieldDropdown.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'HEIGHT'+i}, option[0].height));
|
||||||
|
fieldDropdown.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'ALT'+i}, option[0].alt));
|
||||||
|
}
|
||||||
|
fieldDropdown.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'CPU'+i}, option[1]));
|
||||||
|
}
|
||||||
|
optionsStr = optionsStr.slice(0,-1); // Drop last comma
|
||||||
|
optionsStr += ']';
|
||||||
|
mutation.setAttribute('options', optionsStr);
|
||||||
|
|
||||||
|
return fieldDropdown;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a <block> element representing a FieldCheckbox definition.
|
||||||
|
*
|
||||||
|
* @param {string} fieldName The identifying name of the field.
|
||||||
|
* @param {string} checked The field's default value, true or false.
|
||||||
|
* @return {Element} The XML for FieldCheckbox definition.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.buildFieldCheckbox_ =
|
||||||
|
function(fieldName, checked)
|
||||||
|
{
|
||||||
|
var fieldCheckbox = BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'block', {type: 'field_checkbox'});
|
||||||
|
fieldCheckbox.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'CHECKED'}, checked));
|
||||||
|
fieldCheckbox.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'FIELDNAME'}, fieldName));
|
||||||
|
return fieldCheckbox;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a <block> element representing a FieldColour definition.
|
||||||
|
*
|
||||||
|
* @param {string} fieldName The identifying name of the field.
|
||||||
|
* @param {string} colour The field's default value as a string.
|
||||||
|
* @return {Element} The XML for FieldColour definition.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.buildFieldColour_ =
|
||||||
|
function(fieldName, colour)
|
||||||
|
{
|
||||||
|
var fieldColour = BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'block', {type: 'field_colour'});
|
||||||
|
fieldColour.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'COLOUR'}, colour));
|
||||||
|
fieldColour.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'FIELDNAME'}, fieldName));
|
||||||
|
return fieldColour;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a <block> element representing a FieldVaraible definition.
|
||||||
|
*
|
||||||
|
* @param {string} fieldName The identifying name of the field.
|
||||||
|
* @param {string} varName The variables
|
||||||
|
* @return {Element} The <block> element representing the FieldVariable.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.buildFieldVariable_ = function(fieldName, varName) {
|
||||||
|
var fieldVar = BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'block', {type: 'field_variable'});
|
||||||
|
fieldVar.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'FIELDNAME'}, fieldName));
|
||||||
|
fieldVar.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'TEXT'}, varName));
|
||||||
|
return fieldVar;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a <block> element representing a FieldImage definition.
|
||||||
|
*
|
||||||
|
* @param {string} src The URL of the field image.
|
||||||
|
* @param {number} width The pixel width of the source image
|
||||||
|
* @param {number} height The pixel height of the source image.
|
||||||
|
* @param {string} alt Alterante text to describe image.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.buildFieldImage_ =
|
||||||
|
function(src, width, height, alt)
|
||||||
|
{
|
||||||
|
var block1 = BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'block', {type: 'field_image'});
|
||||||
|
block1.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'SRC'}, src));
|
||||||
|
block1.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'WIDTH'}, width));
|
||||||
|
block1.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'HEIGHT'}, height));
|
||||||
|
block1.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'ALT'}, alt));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a <block> element a group of allowed connection constraint types.
|
||||||
|
*
|
||||||
|
* @param {Array<string>} types List of type names in this group.
|
||||||
|
* @return {Element} The <block> element representing the group, with child
|
||||||
|
* types attached.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.typeGroup_ = function(types) {
|
||||||
|
var typeGroupBlock = BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'block', {type: 'type_group'});
|
||||||
|
typeGroupBlock.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'mutation', {types:types.length}));
|
||||||
|
for (var i=0; i<types.length; i++) {
|
||||||
|
var typeBlock = BlockDefinitionExtractor.buildBlockForType_(types[i]);
|
||||||
|
var valueBlock = BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'value', {name:'TYPE'+i});
|
||||||
|
valueBlock.append(typeBlock);
|
||||||
|
typeGroupBlock.append(valueBlock);
|
||||||
|
}
|
||||||
|
return typeGroupBlock;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a <shadow> block element representing the default null connection
|
||||||
|
* constraint.
|
||||||
|
* @return {Element} The <block> element representing the "null" type
|
||||||
|
* constraint.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.typeNullShadow_ = function() {
|
||||||
|
return BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'shadow', {type: 'type_null'});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a <block> element representing null in a connection constraint.
|
||||||
|
* @return {Element} The <block> element representing the "null" type
|
||||||
|
* constraint.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.typeNull_ = function() {
|
||||||
|
return BlockDefinitionExtractor.newDomElement_('block', {type: 'type_null'});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a <block> element representing the a boolean in a connection
|
||||||
|
* constraint.
|
||||||
|
* @return {Element} The <block> element representing the "boolean" type
|
||||||
|
* constraint.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.typeBoolean_ = function() {
|
||||||
|
return BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'block', {type: 'type_boolean'});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a <block> element representing the a number in a connection
|
||||||
|
* constraint.
|
||||||
|
* @return {Element} The <block> element representing the "number" type
|
||||||
|
* constraint.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.typeNumber_ = function() {
|
||||||
|
return BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'block', {type: 'type_number'});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a <block> element representing the a string in a connection
|
||||||
|
* constraint.
|
||||||
|
* @return {Element} The <block> element representing the "string" type
|
||||||
|
* constraint.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.typeString_ = function() {
|
||||||
|
return BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'block', {type: 'type_string'});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a <block> element representing the a list in a connection
|
||||||
|
* constraint.
|
||||||
|
* @return {Element} The <block> element representing the "list" type
|
||||||
|
* constraint.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.typeList_ = function() {
|
||||||
|
return BlockDefinitionExtractor.newDomElement_('block', {type: 'type_list'});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a <block> element representing the given custom connection
|
||||||
|
* constraint type name.
|
||||||
|
*
|
||||||
|
* @param {string} type The connection constratin type name.
|
||||||
|
* @return {Element} The <block> element representing a custom input type
|
||||||
|
* constraint.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.typeOther_ = function(type) {
|
||||||
|
var block = BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'block', {type: 'type_other'});
|
||||||
|
block.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'TYPE'}, type));
|
||||||
|
return block;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a block Element for the color_hue block, with the given hue.
|
||||||
|
* @param hue {number} The hue value, from 0 to 360.
|
||||||
|
* @return {Element} The <block> Element representing a colour_hue block
|
||||||
|
* with the given hue.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.colourBlockFromHue_ = function(hue) {
|
||||||
|
var colourBlock = BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'block', {type: 'colour_hue'});
|
||||||
|
colourBlock.append(BlockDefinitionExtractor.newDomElement_('mutation', {
|
||||||
|
colour: Blockly.hueToRgb(hue)
|
||||||
|
}));
|
||||||
|
colourBlock.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'HUE'}, hue.toString()));
|
||||||
|
return colourBlock;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a block Element for a text block with the given text.
|
||||||
|
*
|
||||||
|
* @param text {string} The text value of the block.
|
||||||
|
* @return {Element} The <block> element representing a "text" block.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
BlockDefinitionExtractor.text_ = function(text) {
|
||||||
|
var textBlock =
|
||||||
|
BlockDefinitionExtractor.newDomElement_('block', {type: 'text'});
|
||||||
|
if (text) {
|
||||||
|
textBlock.append(BlockDefinitionExtractor.newDomElement_(
|
||||||
|
'field', {name: 'TEXT'}, text));
|
||||||
|
} // Else, use empty string default.
|
||||||
|
return textBlock;
|
||||||
|
};
|
||||||
@@ -37,7 +37,6 @@ goog.provide('BlockFactory');
|
|||||||
goog.require('FactoryUtils');
|
goog.require('FactoryUtils');
|
||||||
goog.require('StandardCategories');
|
goog.require('StandardCategories');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Workspace for user to build block.
|
* Workspace for user to build block.
|
||||||
* @type {Blockly.Workspace}
|
* @type {Blockly.Workspace}
|
||||||
@@ -52,14 +51,32 @@ BlockFactory.previewWorkspace = null;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of block if not named.
|
* Name of block if not named.
|
||||||
|
* @type string
|
||||||
*/
|
*/
|
||||||
BlockFactory.UNNAMED = 'unnamed';
|
BlockFactory.UNNAMED = 'unnamed';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Existing direction ('ltr' vs 'rtl') of preview.
|
* Existing direction ('ltr' vs 'rtl') of preview.
|
||||||
|
* @type string
|
||||||
*/
|
*/
|
||||||
BlockFactory.oldDir = null;
|
BlockFactory.oldDir = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag to signal that an update came from a manual update to the JSON or JavaScript.
|
||||||
|
* definition manually.
|
||||||
|
* @type boolean
|
||||||
|
*/
|
||||||
|
// TODO: Replace global state with parameter passed to functions.
|
||||||
|
BlockFactory.updateBlocksFlag = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delayed flag to avoid infinite update after updating the JSON or JavaScript.
|
||||||
|
* definition manually.
|
||||||
|
* @type boolean
|
||||||
|
*/
|
||||||
|
// TODO: Replace global state with parameter passed to functions.
|
||||||
|
BlockFactory.updateBlocksFlagDelayed = false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The starting XML for the Block Factory main workspace. Contains the
|
* The starting XML for the Block Factory main workspace. Contains the
|
||||||
* unmovable, undeletable factory_base block.
|
* unmovable, undeletable factory_base block.
|
||||||
@@ -85,7 +102,8 @@ BlockFactory.formatChange = function() {
|
|||||||
var mask = document.getElementById('blocklyMask');
|
var mask = document.getElementById('blocklyMask');
|
||||||
var languagePre = document.getElementById('languagePre');
|
var languagePre = document.getElementById('languagePre');
|
||||||
var languageTA = document.getElementById('languageTA');
|
var languageTA = document.getElementById('languageTA');
|
||||||
if (document.getElementById('format').value == 'Manual') {
|
if (document.getElementById('format').value == 'Manual-JSON' ||
|
||||||
|
document.getElementById('format').value == 'Manual-JS') {
|
||||||
Blockly.hideChaff();
|
Blockly.hideChaff();
|
||||||
mask.style.display = 'block';
|
mask.style.display = 'block';
|
||||||
languagePre.style.display = 'none';
|
languagePre.style.display = 'none';
|
||||||
@@ -98,6 +116,9 @@ BlockFactory.formatChange = function() {
|
|||||||
mask.style.display = 'none';
|
mask.style.display = 'none';
|
||||||
languageTA.style.display = 'none';
|
languageTA.style.display = 'none';
|
||||||
languagePre.style.display = 'block';
|
languagePre.style.display = 'block';
|
||||||
|
var code = languagePre.textContent.trim();
|
||||||
|
languageTA.value = code;
|
||||||
|
|
||||||
BlockFactory.updateLanguage();
|
BlockFactory.updateLanguage();
|
||||||
}
|
}
|
||||||
BlockFactory.disableEnableLink();
|
BlockFactory.disableEnableLink();
|
||||||
@@ -115,10 +136,26 @@ BlockFactory.updateLanguage = function() {
|
|||||||
if (!blockType) {
|
if (!blockType) {
|
||||||
blockType = BlockFactory.UNNAMED;
|
blockType = BlockFactory.UNNAMED;
|
||||||
}
|
}
|
||||||
var format = document.getElementById('format').value;
|
|
||||||
var code = FactoryUtils.getBlockDefinition(blockType, rootBlock, format,
|
if (!BlockFactory.updateBlocksFlag) {
|
||||||
BlockFactory.mainWorkspace);
|
var format = document.getElementById('format').value;
|
||||||
FactoryUtils.injectCode(code, 'languagePre');
|
if (format == 'Manual-JSON') {
|
||||||
|
format = 'JSON';
|
||||||
|
} else if (format == 'Manual-JS') {
|
||||||
|
format = 'JavaScript';
|
||||||
|
}
|
||||||
|
|
||||||
|
var code = FactoryUtils.getBlockDefinition(blockType, rootBlock, format,
|
||||||
|
BlockFactory.mainWorkspace);
|
||||||
|
FactoryUtils.injectCode(code, 'languagePre');
|
||||||
|
if (!BlockFactory.updateBlocksFlagDelayed) {
|
||||||
|
var languagePre = document.getElementById('languagePre');
|
||||||
|
var languageTA = document.getElementById('languageTA');
|
||||||
|
code = languagePre.textContent.trim();
|
||||||
|
languageTA.value = code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BlockFactory.updatePreview();
|
BlockFactory.updatePreview();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -151,20 +188,8 @@ BlockFactory.updatePreview = function() {
|
|||||||
}
|
}
|
||||||
BlockFactory.previewWorkspace.clear();
|
BlockFactory.previewWorkspace.clear();
|
||||||
|
|
||||||
// Fetch the code and determine its format (JSON or JavaScript).
|
var format = BlockFactory.getBlockDefinitionFormat();
|
||||||
var format = document.getElementById('format').value;
|
var code = document.getElementById('languageTA').value;
|
||||||
if (format == 'Manual') {
|
|
||||||
var code = document.getElementById('languageTA').value;
|
|
||||||
// If the code is JSON, it will parse, otherwise treat as JS.
|
|
||||||
try {
|
|
||||||
JSON.parse(code);
|
|
||||||
format = 'JSON';
|
|
||||||
} catch (e) {
|
|
||||||
format = 'JavaScript';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var code = document.getElementById('languagePre').textContent;
|
|
||||||
}
|
|
||||||
if (!code.trim()) {
|
if (!code.trim()) {
|
||||||
// Nothing to render. Happens while cloud storage is loading.
|
// Nothing to render. Happens while cloud storage is loading.
|
||||||
return;
|
return;
|
||||||
@@ -188,9 +213,13 @@ BlockFactory.updatePreview = function() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else if (format == 'JavaScript') {
|
} else if (format == 'JavaScript') {
|
||||||
eval(code);
|
try {
|
||||||
} else {
|
eval(code);
|
||||||
throw 'Unknown format: ' + format;
|
} catch (e) {
|
||||||
|
// TODO: Display error in the UI
|
||||||
|
console.error("Error while evaluating JavaScript formatted block definition", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for a block on Blockly.Blocks that does not match the backup.
|
// Look for a block on Blockly.Blocks that does not match the backup.
|
||||||
@@ -232,12 +261,36 @@ BlockFactory.updatePreview = function() {
|
|||||||
} else {
|
} else {
|
||||||
rootBlock.setWarningText(null);
|
rootBlock.setWarningText(null);
|
||||||
}
|
}
|
||||||
|
} catch(err) {
|
||||||
|
// TODO: Show error on the UI
|
||||||
|
console.log(err);
|
||||||
|
BlockFactory.updateBlocksFlag = false
|
||||||
|
BlockFactory.updateBlocksFlagDelayed = false
|
||||||
} finally {
|
} finally {
|
||||||
Blockly.Blocks = backupBlocks;
|
Blockly.Blocks = backupBlocks;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the format from the Block Definitions' format selector/drop-down.
|
||||||
|
* @return Either 'JavaScript' or 'JSON'.
|
||||||
|
* @throws If selector value is not recognized.
|
||||||
|
*/
|
||||||
|
BlockFactory.getBlockDefinitionFormat = function() {
|
||||||
|
switch (document.getElementById('format').value) {
|
||||||
|
case 'JSON':
|
||||||
|
case 'Manual-JSON':
|
||||||
|
return 'JSON';
|
||||||
|
|
||||||
|
case 'JavaScript':
|
||||||
|
case 'Manual-JS':
|
||||||
|
return 'JavaScript';
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw 'Unknown format: ' + format;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable link and save buttons if the format is 'Manual', enable otherwise.
|
* Disable link and save buttons if the format is 'Manual', enable otherwise.
|
||||||
*/
|
*/
|
||||||
@@ -245,7 +298,7 @@ BlockFactory.disableEnableLink = function() {
|
|||||||
var linkButton = document.getElementById('linkButton');
|
var linkButton = document.getElementById('linkButton');
|
||||||
var saveBlockButton = document.getElementById('localSaveButton');
|
var saveBlockButton = document.getElementById('localSaveButton');
|
||||||
var saveToLibButton = document.getElementById('saveToBlockLibraryButton');
|
var saveToLibButton = document.getElementById('saveToBlockLibraryButton');
|
||||||
var disabled = document.getElementById('format').value == 'Manual';
|
var disabled = document.getElementById('format').value.substr(0, 6) == 'Manual';
|
||||||
linkButton.disabled = disabled;
|
linkButton.disabled = disabled;
|
||||||
saveBlockButton.disabled = disabled;
|
saveBlockButton.disabled = disabled;
|
||||||
saveToLibButton.disabled = disabled;
|
saveToLibButton.disabled = disabled;
|
||||||
@@ -265,12 +318,25 @@ BlockFactory.showStarterBlock = function() {
|
|||||||
*/
|
*/
|
||||||
BlockFactory.isStarterBlock = function() {
|
BlockFactory.isStarterBlock = function() {
|
||||||
var rootBlock = FactoryUtils.getRootBlock(BlockFactory.mainWorkspace);
|
var rootBlock = FactoryUtils.getRootBlock(BlockFactory.mainWorkspace);
|
||||||
// The starter block does not have blocks nested into the factory_base block.
|
return rootBlock && !(
|
||||||
return !(rootBlock.getChildren().length > 0 ||
|
// The starter block does not have blocks nested into the factory_base block.
|
||||||
|
rootBlock.getChildren().length > 0 ||
|
||||||
// The starter block's name is the default, 'block_type'.
|
// The starter block's name is the default, 'block_type'.
|
||||||
rootBlock.getFieldValue('NAME').trim().toLowerCase() != 'block_type' ||
|
rootBlock.getFieldValue('NAME').trim().toLowerCase() != 'block_type' ||
|
||||||
// The starter block has no connections.
|
// The starter block has no connections.
|
||||||
rootBlock.getFieldValue('CONNECTIONS') != 'NONE' ||
|
rootBlock.getFieldValue('CONNECTIONS') != 'NONE' ||
|
||||||
// The starter block has automatic inputs.
|
// The starter block has automatic inputs.
|
||||||
rootBlock.getFieldValue('INLINE') != 'AUTO');
|
rootBlock.getFieldValue('INLINE') != 'AUTO'
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates blocks from the manually edited js or json from their text area.
|
||||||
|
*/
|
||||||
|
BlockFactory.manualEdit = function() {
|
||||||
|
// TODO(#1267): Replace these global state flags with parameters passed to
|
||||||
|
// the right functions.
|
||||||
|
BlockFactory.updateBlocksFlag = true;
|
||||||
|
BlockFactory.updateBlocksFlagDelayed = true;
|
||||||
|
BlockFactory.updateLanguage();
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,15 +24,18 @@
|
|||||||
* Exporter applications within Blockly Factory. Holds functions to generate
|
* Exporter applications within Blockly Factory. Holds functions to generate
|
||||||
* block definitions and generator stubs and to create and download files.
|
* block definitions and generator stubs and to create and download files.
|
||||||
*
|
*
|
||||||
* @author fraser@google.com (Neil Fraser), quachtina96 (Tina Quach)
|
* @author fraser@google.com (Neil Fraser), quachtina96 (Tina Quach), JC-Orozco
|
||||||
|
* (Juan Carlos Orozco)
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Namespace for FactoryUtils.
|
* Namespace for FactoryUtils.
|
||||||
*/
|
*/
|
||||||
goog.provide('FactoryUtils');
|
goog.provide('FactoryUtils');
|
||||||
|
|
||||||
|
goog.require('BlockDefinitionExtractor');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get block definition code for the current block.
|
* Get block definition code for the current block.
|
||||||
@@ -73,10 +76,23 @@ FactoryUtils.cleanBlockType = function(blockType) {
|
|||||||
* Get the generator code for a given block.
|
* Get the generator code for a given block.
|
||||||
* @param {!Blockly.Block} block Rendered block in preview workspace.
|
* @param {!Blockly.Block} block Rendered block in preview workspace.
|
||||||
* @param {string} generatorLanguage 'JavaScript', 'Python', 'PHP', 'Lua',
|
* @param {string} generatorLanguage 'JavaScript', 'Python', 'PHP', 'Lua',
|
||||||
* 'Dart'.
|
* or 'Dart'.
|
||||||
* @return {string} Generator code for multiple blocks.
|
* @return {string} Generator code for multiple blocks.
|
||||||
*/
|
*/
|
||||||
FactoryUtils.getGeneratorStub = function(block, generatorLanguage) {
|
FactoryUtils.getGeneratorStub = function(block, generatorLanguage) {
|
||||||
|
// Build factory blocks from block
|
||||||
|
if (BlockFactory.updateBlocksFlag) { // TODO: Move this to updatePreview()
|
||||||
|
BlockFactory.mainWorkspace.clear();
|
||||||
|
var xml = BlockDefinitionExtractor.buildBlockFactoryWorkspace(block);
|
||||||
|
Blockly.Xml.domToWorkspace(xml, BlockFactory.mainWorkspace);
|
||||||
|
// Calculate timer to avoid infinite update loops
|
||||||
|
// TODO(#1267): Remove the global variables and any infinite loops.
|
||||||
|
BlockFactory.updateBlocksFlag = false;
|
||||||
|
setTimeout(
|
||||||
|
function() {BlockFactory.updateBlocksFlagDelayed = false;}, 3000);
|
||||||
|
}
|
||||||
|
BlockFactory.lastUpdatedBlock = block; // Variable to share the block value.
|
||||||
|
|
||||||
function makeVar(root, name) {
|
function makeVar(root, name) {
|
||||||
name = name.toLowerCase().replace(/\W/g, '_');
|
name = name.toLowerCase().replace(/\W/g, '_');
|
||||||
return ' var ' + root + '_' + name;
|
return ' var ' + root + '_' + name;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<script src="../../blocks_compressed.js"></script>
|
<script src="../../blocks_compressed.js"></script>
|
||||||
<script src="../../../closure-library/closure/goog/base.js"></script>
|
<script src="../../../closure-library/closure/goog/base.js"></script>
|
||||||
<script src="analytics.js"></script>
|
<script src="analytics.js"></script>
|
||||||
|
<script src="block_definition_extractor.js"></script>
|
||||||
<script src="factory_utils.js"></script>
|
<script src="factory_utils.js"></script>
|
||||||
<script src="workspacefactory/wfactory_model.js"></script>
|
<script src="workspacefactory/wfactory_model.js"></script>
|
||||||
<script src="standard_categories.js"></script>
|
<script src="standard_categories.js"></script>
|
||||||
@@ -354,10 +355,21 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td height="5%">
|
<td height="5%">
|
||||||
<h3>Block Definition:
|
<h3>Block Definition:
|
||||||
|
<!-- TODO(#1268): Separate concerns of format and editable.
|
||||||
|
- Add "Editable" state toggle button? -->
|
||||||
<select id="format">
|
<select id="format">
|
||||||
<option value="JSON">JSON</option>
|
<option value="JSON">JSON</option>
|
||||||
<option value="JavaScript">JavaScript</option>
|
<option value="JavaScript">JavaScript</option>
|
||||||
<option value="Manual">Manual edit…</option>
|
<option value="Manual-JSON">Manual JSON…</option>
|
||||||
|
<script>
|
||||||
|
// Manual JavaScript works but requires use of eval().
|
||||||
|
// TODO(#1269): Replace eval() with JS-Interpreter before
|
||||||
|
// re-enabling "Manual JavaScript" mode.
|
||||||
|
if (document.location.href.indexOf('file://') == 0) {
|
||||||
|
document.write(
|
||||||
|
'<option value="Manual-JS">Manual JavaScript…</option>');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</select>
|
</select>
|
||||||
</h3>
|
</h3>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
Reference in New Issue
Block a user