mirror of
https://github.com/google/blockly.git
synced 2026-01-04 15:40:08 +01:00
There is significant confusion in names and comments with regards to variables and procedures. `Blockly.Generator.prototype.variableDB_` is a Blockly.Names database, not a variable map. This rename introduces a getter and setter so deprecated references still work. This commit also fixes some comments which are either outright wrong or misleading regarding variable and procedure names.
821 lines
26 KiB
JavaScript
821 lines
26 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2012 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @fileoverview JavaScript for Blockly's Block Factory application.
|
|
* @author fraser@google.com (Neil Fraser)
|
|
*/
|
|
'use strict';
|
|
|
|
/**
|
|
* Workspace for user to build block.
|
|
* @type {Blockly.Workspace}
|
|
*/
|
|
var mainWorkspace = null;
|
|
|
|
/**
|
|
* Workspace for preview of block.
|
|
* @type {Blockly.Workspace}
|
|
*/
|
|
var previewWorkspace = null;
|
|
|
|
/**
|
|
* Name of block if not named.
|
|
*/
|
|
var UNNAMED = 'unnamed';
|
|
|
|
/**
|
|
* Change the language code format.
|
|
*/
|
|
function formatChange() {
|
|
var mask = document.getElementById('blocklyMask');
|
|
var languagePre = document.getElementById('languagePre');
|
|
var languageTA = document.getElementById('languageTA');
|
|
if (document.getElementById('format').value == 'Manual') {
|
|
Blockly.hideChaff();
|
|
mask.style.display = 'block';
|
|
languagePre.style.display = 'none';
|
|
languageTA.style.display = 'block';
|
|
var code = languagePre.textContent.trim();
|
|
languageTA.value = code;
|
|
languageTA.focus();
|
|
updatePreview();
|
|
} else {
|
|
mask.style.display = 'none';
|
|
languageTA.style.display = 'none';
|
|
languagePre.style.display = 'block';
|
|
updateLanguage();
|
|
}
|
|
disableEnableLink();
|
|
}
|
|
|
|
/**
|
|
* Update the language code based on constructs made in Blockly.
|
|
*/
|
|
function updateLanguage() {
|
|
var rootBlock = getRootBlock();
|
|
if (!rootBlock) {
|
|
return;
|
|
}
|
|
var blockType = rootBlock.getFieldValue('NAME').trim().toLowerCase();
|
|
if (!blockType) {
|
|
blockType = UNNAMED;
|
|
}
|
|
blockType = blockType.replace(/\W/g, '_').replace(/^(\d)/, '_\\1');
|
|
switch (document.getElementById('format').value) {
|
|
case 'JSON':
|
|
var code = formatJson_(blockType, rootBlock);
|
|
break;
|
|
case 'JavaScript':
|
|
var code = formatJavaScript_(blockType, rootBlock);
|
|
break;
|
|
}
|
|
injectCode(code, 'languagePre');
|
|
updatePreview();
|
|
}
|
|
|
|
/**
|
|
* Update the language code as JSON.
|
|
* @param {string} blockType Name of block.
|
|
* @param {!Blockly.Block} rootBlock Factory_base block.
|
|
* @return {string} Generated language code.
|
|
* @private
|
|
*/
|
|
function formatJson_(blockType, rootBlock) {
|
|
var JS = {};
|
|
// Type is not used by Blockly, but may be used by a loader.
|
|
JS.type = blockType;
|
|
// Generate inputs.
|
|
var message = [];
|
|
var args = [];
|
|
var contentsBlock = rootBlock.getInputTargetBlock('INPUTS');
|
|
var lastInput = null;
|
|
while (contentsBlock) {
|
|
if (!contentsBlock.disabled && !contentsBlock.getInheritedDisabled()) {
|
|
var fields = getFieldsJson_(contentsBlock.getInputTargetBlock('FIELDS'));
|
|
for (var i = 0; i < fields.length; i++) {
|
|
if (typeof fields[i] == 'string') {
|
|
message.push(fields[i].replace(/%/g, '%%'));
|
|
} else {
|
|
args.push(fields[i]);
|
|
message.push('%' + args.length);
|
|
}
|
|
}
|
|
|
|
var input = {type: contentsBlock.type};
|
|
// Dummy inputs don't have names. Other inputs do.
|
|
if (contentsBlock.type != 'input_dummy') {
|
|
input.name = contentsBlock.getFieldValue('INPUTNAME');
|
|
}
|
|
var check = JSON.parse(getOptTypesFrom(contentsBlock, 'TYPE') || 'null');
|
|
if (check) {
|
|
input.check = check;
|
|
}
|
|
var align = contentsBlock.getFieldValue('ALIGN');
|
|
if (align != 'LEFT') {
|
|
input.align = align;
|
|
}
|
|
args.push(input);
|
|
message.push('%' + args.length);
|
|
lastInput = contentsBlock;
|
|
}
|
|
contentsBlock = contentsBlock.nextConnection &&
|
|
contentsBlock.nextConnection.targetBlock();
|
|
}
|
|
// Remove last input if dummy and not empty.
|
|
if (lastInput && lastInput.type == 'input_dummy') {
|
|
var fields = lastInput.getInputTargetBlock('FIELDS');
|
|
if (fields && getFieldsJson_(fields).join('').trim() != '') {
|
|
var align = lastInput.getFieldValue('ALIGN');
|
|
if (align != 'LEFT') {
|
|
JS.lastDummyAlign0 = align;
|
|
}
|
|
args.pop();
|
|
message.pop();
|
|
}
|
|
}
|
|
JS.message0 = message.join(' ');
|
|
if (args.length) {
|
|
JS.args0 = args;
|
|
}
|
|
// Generate inline/external switch.
|
|
if (rootBlock.getFieldValue('INLINE') == 'EXT') {
|
|
JS.inputsInline = false;
|
|
} else if (rootBlock.getFieldValue('INLINE') == 'INT') {
|
|
JS.inputsInline = true;
|
|
}
|
|
// Generate output, or next/previous connections.
|
|
switch (rootBlock.getFieldValue('CONNECTIONS')) {
|
|
case 'LEFT':
|
|
JS.output =
|
|
JSON.parse(getOptTypesFrom(rootBlock, 'OUTPUTTYPE') || 'null');
|
|
break;
|
|
case 'BOTH':
|
|
JS.previousStatement =
|
|
JSON.parse(getOptTypesFrom(rootBlock, 'TOPTYPE') || 'null');
|
|
JS.nextStatement =
|
|
JSON.parse(getOptTypesFrom(rootBlock, 'BOTTOMTYPE') || 'null');
|
|
break;
|
|
case 'TOP':
|
|
JS.previousStatement =
|
|
JSON.parse(getOptTypesFrom(rootBlock, 'TOPTYPE') || 'null');
|
|
break;
|
|
case 'BOTTOM':
|
|
JS.nextStatement =
|
|
JSON.parse(getOptTypesFrom(rootBlock, 'BOTTOMTYPE') || 'null');
|
|
break;
|
|
}
|
|
// Generate colour.
|
|
var colourBlock = rootBlock.getInputTargetBlock('COLOUR');
|
|
if (colourBlock && !colourBlock.disabled) {
|
|
var hue = parseInt(colourBlock.getFieldValue('HUE'), 10);
|
|
JS.colour = hue;
|
|
}
|
|
JS.tooltip = '';
|
|
JS.helpUrl = 'http://www.example.com/';
|
|
return JSON.stringify(JS, null, ' ');
|
|
}
|
|
|
|
/**
|
|
* Update the language code as JavaScript.
|
|
* @param {string} blockType Name of block.
|
|
* @param {!Blockly.Block} rootBlock Factory_base block.
|
|
* @return {string} Generated language code.
|
|
* @private
|
|
*/
|
|
function formatJavaScript_(blockType, rootBlock) {
|
|
var code = [];
|
|
code.push("Blockly.Blocks['" + blockType + "'] = {");
|
|
code.push(" init: function() {");
|
|
// Generate inputs.
|
|
var TYPES = {'input_value': 'appendValueInput',
|
|
'input_statement': 'appendStatementInput',
|
|
'input_dummy': 'appendDummyInput'};
|
|
var contentsBlock = rootBlock.getInputTargetBlock('INPUTS');
|
|
while (contentsBlock) {
|
|
if (!contentsBlock.disabled && !contentsBlock.getInheritedDisabled()) {
|
|
var name = '';
|
|
// Dummy inputs don't have names. Other inputs do.
|
|
if (contentsBlock.type != 'input_dummy') {
|
|
name = escapeString(contentsBlock.getFieldValue('INPUTNAME'));
|
|
}
|
|
code.push(' this.' + TYPES[contentsBlock.type] + '(' + name + ')');
|
|
var check = getOptTypesFrom(contentsBlock, 'TYPE');
|
|
if (check) {
|
|
code.push(' .setCheck(' + check + ')');
|
|
}
|
|
var align = contentsBlock.getFieldValue('ALIGN');
|
|
if (align != 'LEFT') {
|
|
code.push(' .setAlign(Blockly.ALIGN_' + align + ')');
|
|
}
|
|
var fields = getFieldsJs_(contentsBlock.getInputTargetBlock('FIELDS'));
|
|
for (var i = 0; i < fields.length; i++) {
|
|
code.push(' .appendField(' + fields[i] + ')');
|
|
}
|
|
// Add semicolon to last line to finish the statement.
|
|
code[code.length - 1] += ';';
|
|
}
|
|
contentsBlock = contentsBlock.nextConnection &&
|
|
contentsBlock.nextConnection.targetBlock();
|
|
}
|
|
// Generate inline/external switch.
|
|
if (rootBlock.getFieldValue('INLINE') == 'EXT') {
|
|
code.push(' this.setInputsInline(false);');
|
|
} else if (rootBlock.getFieldValue('INLINE') == 'INT') {
|
|
code.push(' this.setInputsInline(true);');
|
|
}
|
|
// Generate output, or next/previous connections.
|
|
switch (rootBlock.getFieldValue('CONNECTIONS')) {
|
|
case 'LEFT':
|
|
code.push(connectionLineJs_('setOutput', 'OUTPUTTYPE'));
|
|
break;
|
|
case 'BOTH':
|
|
code.push(connectionLineJs_('setPreviousStatement', 'TOPTYPE'));
|
|
code.push(connectionLineJs_('setNextStatement', 'BOTTOMTYPE'));
|
|
break;
|
|
case 'TOP':
|
|
code.push(connectionLineJs_('setPreviousStatement', 'TOPTYPE'));
|
|
break;
|
|
case 'BOTTOM':
|
|
code.push(connectionLineJs_('setNextStatement', 'BOTTOMTYPE'));
|
|
break;
|
|
}
|
|
// Generate colour.
|
|
var colourBlock = rootBlock.getInputTargetBlock('COLOUR');
|
|
if (colourBlock && !colourBlock.disabled) {
|
|
var hue = parseInt(colourBlock.getFieldValue('HUE'), 10);
|
|
if (!isNaN(hue)) {
|
|
code.push(' this.setColour(' + hue + ');');
|
|
}
|
|
}
|
|
code.push(" this.setTooltip('');");
|
|
code.push(" this.setHelpUrl('http://www.example.com/');");
|
|
code.push(' }');
|
|
code.push('};');
|
|
return code.join('\n');
|
|
}
|
|
|
|
/**
|
|
* Create JS code required to create a top, bottom, or value connection.
|
|
* @param {string} functionName JavaScript function name.
|
|
* @param {string} typeName Name of type input.
|
|
* @return {string} Line of JavaScript code to create connection.
|
|
* @private
|
|
*/
|
|
function connectionLineJs_(functionName, typeName) {
|
|
var type = getOptTypesFrom(getRootBlock(), typeName);
|
|
if (type) {
|
|
type = ', ' + type;
|
|
} else {
|
|
type = '';
|
|
}
|
|
return ' this.' + functionName + '(true' + type + ');';
|
|
}
|
|
|
|
/**
|
|
* Returns field strings and any config.
|
|
* @param {!Blockly.Block} block Input block.
|
|
* @return {!Array.<string>} Field strings.
|
|
* @private
|
|
*/
|
|
function getFieldsJs_(block) {
|
|
var fields = [];
|
|
while (block) {
|
|
if (!block.disabled && !block.getInheritedDisabled()) {
|
|
switch (block.type) {
|
|
case 'field_static':
|
|
// Result: 'hello'
|
|
fields.push(escapeString(block.getFieldValue('TEXT')));
|
|
break;
|
|
case 'field_input':
|
|
// Result: new Blockly.FieldTextInput('Hello'), 'GREET'
|
|
fields.push('new Blockly.FieldTextInput(' +
|
|
escapeString(block.getFieldValue('TEXT')) + '), ' +
|
|
escapeString(block.getFieldValue('FIELDNAME')));
|
|
break;
|
|
case 'field_number':
|
|
// Result: new Blockly.FieldNumber(10, 0, 100, 1), 'NUMBER'
|
|
var args = [
|
|
Number(block.getFieldValue('VALUE')),
|
|
Number(block.getFieldValue('MIN')),
|
|
Number(block.getFieldValue('MAX')),
|
|
Number(block.getFieldValue('PRECISION'))
|
|
];
|
|
// Remove any trailing arguments that aren't needed.
|
|
if (args[3] == 0) {
|
|
args.pop();
|
|
if (args[2] == Infinity) {
|
|
args.pop();
|
|
if (args[1] == -Infinity) {
|
|
args.pop();
|
|
}
|
|
}
|
|
}
|
|
fields.push('new Blockly.FieldNumber(' + args.join(', ') + '), ' +
|
|
escapeString(block.getFieldValue('FIELDNAME')));
|
|
break;
|
|
case 'field_angle':
|
|
// Result: new Blockly.FieldAngle(90), 'ANGLE'
|
|
fields.push('new Blockly.FieldAngle(' +
|
|
Number(block.getFieldValue('ANGLE')) + '), ' +
|
|
escapeString(block.getFieldValue('FIELDNAME')));
|
|
break;
|
|
case 'field_checkbox':
|
|
// Result: new Blockly.FieldCheckbox('TRUE'), 'CHECK'
|
|
fields.push('new Blockly.FieldCheckbox(' +
|
|
escapeString(block.getFieldValue('CHECKED')) + '), ' +
|
|
escapeString(block.getFieldValue('FIELDNAME')));
|
|
break;
|
|
case 'field_colour':
|
|
// Result: new Blockly.FieldColour('#ff0000'), 'COLOUR'
|
|
fields.push('new Blockly.FieldColour(' +
|
|
escapeString(block.getFieldValue('COLOUR')) + '), ' +
|
|
escapeString(block.getFieldValue('FIELDNAME')));
|
|
break;
|
|
case 'field_variable':
|
|
// Result: new Blockly.FieldVariable('item'), 'VAR'
|
|
var varname = escapeString(block.getFieldValue('TEXT') || null);
|
|
fields.push('new Blockly.FieldVariable(' + varname + '), ' +
|
|
escapeString(block.getFieldValue('FIELDNAME')));
|
|
break;
|
|
case 'field_dropdown':
|
|
// Result:
|
|
// new Blockly.FieldDropdown([['yes', '1'], ['no', '0']]), 'TOGGLE'
|
|
var options = [];
|
|
for (var i = 0; i < block.optionCount_; i++) {
|
|
options[i] = '[' + escapeString(block.getFieldValue('USER' + i)) +
|
|
', ' + escapeString(block.getFieldValue('CPU' + i)) + ']';
|
|
}
|
|
if (options.length) {
|
|
fields.push('new Blockly.FieldDropdown([' +
|
|
options.join(', ') + ']), ' +
|
|
escapeString(block.getFieldValue('FIELDNAME')));
|
|
}
|
|
break;
|
|
case 'field_image':
|
|
// Result: new Blockly.FieldImage('http://...', 80, 60, '*')
|
|
var src = escapeString(block.getFieldValue('SRC'));
|
|
var width = Number(block.getFieldValue('WIDTH'));
|
|
var height = Number(block.getFieldValue('HEIGHT'));
|
|
var alt = escapeString(block.getFieldValue('ALT'));
|
|
fields.push('new Blockly.FieldImage(' +
|
|
src + ', ' + width + ', ' + height + ', ' + alt + ')');
|
|
break;
|
|
}
|
|
}
|
|
block = block.nextConnection && block.nextConnection.targetBlock();
|
|
}
|
|
return fields;
|
|
}
|
|
|
|
/**
|
|
* Returns field strings and any config.
|
|
* @param {!Blockly.Block} block Input block.
|
|
* @return {!Array.<string|!Object>} Array of static text and field configs.
|
|
* @private
|
|
*/
|
|
function getFieldsJson_(block) {
|
|
var fields = [];
|
|
while (block) {
|
|
if (!block.disabled && !block.getInheritedDisabled()) {
|
|
switch (block.type) {
|
|
case 'field_static':
|
|
// Result: 'hello'
|
|
fields.push(block.getFieldValue('TEXT'));
|
|
break;
|
|
case 'field_input':
|
|
fields.push({
|
|
type: block.type,
|
|
name: block.getFieldValue('FIELDNAME'),
|
|
text: block.getFieldValue('TEXT')
|
|
});
|
|
break;
|
|
case 'field_number':
|
|
var obj = {
|
|
type: block.type,
|
|
name: block.getFieldValue('FIELDNAME'),
|
|
value: Number(block.getFieldValue('VALUE'))
|
|
};
|
|
var min = Number(block.getFieldValue('MIN'));
|
|
if (min > -Infinity) {
|
|
obj.min = min;
|
|
}
|
|
var max = Number(block.getFieldValue('MAX'));
|
|
if (max < Infinity) {
|
|
obj.max = max;
|
|
}
|
|
var precision = Number(block.getFieldValue('PRECISION'));
|
|
if (precision) {
|
|
obj.precision = precision;
|
|
}
|
|
fields.push(obj);
|
|
break;
|
|
case 'field_angle':
|
|
fields.push({
|
|
type: block.type,
|
|
name: block.getFieldValue('FIELDNAME'),
|
|
angle: Number(block.getFieldValue('ANGLE'))
|
|
});
|
|
break;
|
|
case 'field_checkbox':
|
|
fields.push({
|
|
type: block.type,
|
|
name: block.getFieldValue('FIELDNAME'),
|
|
checked: block.getFieldValue('CHECKED') == 'TRUE'
|
|
});
|
|
break;
|
|
case 'field_colour':
|
|
fields.push({
|
|
type: block.type,
|
|
name: block.getFieldValue('FIELDNAME'),
|
|
colour: block.getFieldValue('COLOUR')
|
|
});
|
|
break;
|
|
case 'field_variable':
|
|
fields.push({
|
|
type: block.type,
|
|
name: block.getFieldValue('FIELDNAME'),
|
|
variable: block.getFieldValue('TEXT') || null
|
|
});
|
|
break;
|
|
case 'field_dropdown':
|
|
var options = [];
|
|
for (var i = 0; i < block.optionCount_; i++) {
|
|
options[i] = [block.getFieldValue('USER' + i),
|
|
block.getFieldValue('CPU' + i)];
|
|
}
|
|
if (options.length) {
|
|
fields.push({
|
|
type: block.type,
|
|
name: block.getFieldValue('FIELDNAME'),
|
|
options: options
|
|
});
|
|
}
|
|
break;
|
|
case 'field_image':
|
|
fields.push({
|
|
type: block.type,
|
|
src: block.getFieldValue('SRC'),
|
|
width: Number(block.getFieldValue('WIDTH')),
|
|
height: Number(block.getFieldValue('HEIGHT')),
|
|
alt: block.getFieldValue('ALT')
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
block = block.nextConnection && block.nextConnection.targetBlock();
|
|
}
|
|
return fields;
|
|
}
|
|
|
|
/**
|
|
* Escape a string.
|
|
* @param {string} string String to escape.
|
|
* @return {string} Escaped string surrounded by quotes.
|
|
*/
|
|
function escapeString(string) {
|
|
return JSON.stringify(string);
|
|
}
|
|
|
|
/**
|
|
* Fetch the type(s) defined in the given input.
|
|
* Format as a string for appending to the generated code.
|
|
* @param {!Blockly.Block} block Block with input.
|
|
* @param {string} name Name of the input.
|
|
* @return {?string} String defining the types.
|
|
*/
|
|
function getOptTypesFrom(block, name) {
|
|
var types = getTypesFrom_(block, name);
|
|
if (types.length == 0) {
|
|
return undefined;
|
|
} else if (types.indexOf('null') != -1) {
|
|
return 'null';
|
|
} else if (types.length == 1) {
|
|
return types[0];
|
|
} else {
|
|
return '[' + types.join(', ') + ']';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch the type(s) defined in the given input.
|
|
* @param {!Blockly.Block} block Block with input.
|
|
* @param {string} name Name of the input.
|
|
* @return {!Array.<string>} List of types.
|
|
* @private
|
|
*/
|
|
function getTypesFrom_(block, name) {
|
|
var typeBlock = block.getInputTargetBlock(name);
|
|
var types;
|
|
if (!typeBlock || typeBlock.disabled) {
|
|
types = [];
|
|
} else if (typeBlock.type == 'type_other') {
|
|
types = [escapeString(typeBlock.getFieldValue('TYPE'))];
|
|
} else if (typeBlock.type == 'type_group') {
|
|
types = [];
|
|
for (var i = 0; i < typeBlock.typeCount_; i++) {
|
|
types = types.concat(getTypesFrom_(typeBlock, 'TYPE' + i));
|
|
}
|
|
// Remove duplicates.
|
|
var hash = Object.create(null);
|
|
for (var n = types.length - 1; n >= 0; n--) {
|
|
if (hash[types[n]]) {
|
|
types.splice(n, 1);
|
|
}
|
|
hash[types[n]] = true;
|
|
}
|
|
} else {
|
|
types = [escapeString(typeBlock.valueType)];
|
|
}
|
|
return types;
|
|
}
|
|
|
|
/**
|
|
* Update the generator code.
|
|
* @param {!Blockly.Block} block Rendered block in preview workspace.
|
|
*/
|
|
function updateGenerator(block) {
|
|
function makeVar(root, name) {
|
|
name = name.toLowerCase().replace(/\W/g, '_');
|
|
return ' var ' + root + '_' + name;
|
|
}
|
|
var language = document.getElementById('language').value;
|
|
var code = [];
|
|
code.push("Blockly." + language + "['" + block.type +
|
|
"'] = function(block) {");
|
|
|
|
// Generate getters for any fields or inputs.
|
|
for (var i = 0, input; input = block.inputList[i]; i++) {
|
|
for (var j = 0, field; field = input.fieldRow[j]; j++) {
|
|
var name = field.name;
|
|
if (!name) {
|
|
continue;
|
|
}
|
|
if (field instanceof Blockly.FieldVariable) {
|
|
// Subclass of Blockly.FieldDropdown, must test first.
|
|
code.push(makeVar('variable', name) +
|
|
" = Blockly." + language +
|
|
".nameDB_.getName(block.getFieldValue('" + name +
|
|
"'), Blockly.Variables.NAME_TYPE);");
|
|
} else if (field instanceof Blockly.FieldAngle) {
|
|
// Subclass of Blockly.FieldTextInput, must test first.
|
|
code.push(makeVar('angle', name) +
|
|
" = block.getFieldValue('" + name + "');");
|
|
} else if (field instanceof Blockly.FieldColour) {
|
|
code.push(makeVar('colour', name) +
|
|
" = block.getFieldValue('" + name + "');");
|
|
} else if (field instanceof Blockly.FieldCheckbox) {
|
|
code.push(makeVar('checkbox', name) +
|
|
" = block.getFieldValue('" + name + "') == 'TRUE';");
|
|
} else if (field instanceof Blockly.FieldDropdown) {
|
|
code.push(makeVar('dropdown', name) +
|
|
" = block.getFieldValue('" + name + "');");
|
|
} else if (field instanceof Blockly.FieldNumber) {
|
|
code.push(makeVar('number', name) +
|
|
" = block.getFieldValue('" + name + "');");
|
|
} else if (field instanceof Blockly.FieldTextInput) {
|
|
code.push(makeVar('text', name) +
|
|
" = block.getFieldValue('" + name + "');");
|
|
}
|
|
}
|
|
var name = input.name;
|
|
if (name) {
|
|
if (input.type == Blockly.INPUT_VALUE) {
|
|
code.push(makeVar('value', name) +
|
|
" = Blockly." + language + ".valueToCode(block, '" + name +
|
|
"', Blockly." + language + ".ORDER_ATOMIC);");
|
|
} else if (input.type == Blockly.NEXT_STATEMENT) {
|
|
code.push(makeVar('statements', name) +
|
|
" = Blockly." + language + ".statementToCode(block, '" +
|
|
name + "');");
|
|
}
|
|
}
|
|
}
|
|
// Most languages end lines with a semicolon. Python does not.
|
|
var lineEnd = {
|
|
'JavaScript': ';',
|
|
'Python': '',
|
|
'PHP': ';',
|
|
'Dart': ';'
|
|
};
|
|
code.push(" // TODO: Assemble " + language + " into code variable.");
|
|
if (block.outputConnection) {
|
|
code.push(" var code = '...';");
|
|
code.push(" // TODO: Change ORDER_NONE to the correct strength.");
|
|
code.push(" return [code, Blockly." + language + ".ORDER_NONE];");
|
|
} else {
|
|
code.push(" var code = '..." + (lineEnd[language] || '') + "\\n';");
|
|
code.push(" return code;");
|
|
}
|
|
code.push("};");
|
|
|
|
injectCode(code.join('\n'), 'generatorPre');
|
|
}
|
|
|
|
/**
|
|
* Existing direction ('ltr' vs 'rtl') of preview.
|
|
*/
|
|
var oldDir = null;
|
|
|
|
/**
|
|
* Update the preview display.
|
|
*/
|
|
function updatePreview() {
|
|
// Toggle between LTR/RTL if needed (also used in first display).
|
|
var newDir = document.getElementById('direction').value;
|
|
if (oldDir != newDir) {
|
|
if (previewWorkspace) {
|
|
previewWorkspace.dispose();
|
|
}
|
|
var rtl = newDir == 'rtl';
|
|
previewWorkspace = Blockly.inject('preview',
|
|
{rtl: rtl,
|
|
media: '../../media/',
|
|
scrollbars: true});
|
|
oldDir = newDir;
|
|
}
|
|
previewWorkspace.clear();
|
|
|
|
// Fetch the code and determine its format (JSON or JavaScript).
|
|
var format = document.getElementById('format').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()) {
|
|
// Nothing to render. Happens while cloud storage is loading.
|
|
return;
|
|
}
|
|
|
|
// Backup Blockly.Blocks object so that main workspace and preview don't
|
|
// collide if user creates a 'factory_base' block, for instance.
|
|
var backupBlocks = Blockly.Blocks;
|
|
try {
|
|
// Make a shallow copy.
|
|
Blockly.Blocks = {};
|
|
for (var prop in backupBlocks) {
|
|
Blockly.Blocks[prop] = backupBlocks[prop];
|
|
}
|
|
|
|
if (format == 'JSON') {
|
|
var json = JSON.parse(code);
|
|
Blockly.Blocks[json.type || UNNAMED] = {
|
|
init: function() {
|
|
this.jsonInit(json);
|
|
}
|
|
};
|
|
} else if (format == 'JavaScript') {
|
|
eval(code);
|
|
} else {
|
|
throw 'Unknown format: ' + format;
|
|
}
|
|
|
|
// Look for a block on Blockly.Blocks that does not match the backup.
|
|
var blockType = null;
|
|
for (var type in Blockly.Blocks) {
|
|
if (typeof Blockly.Blocks[type].init == 'function' &&
|
|
Blockly.Blocks[type] != backupBlocks[type]) {
|
|
blockType = type;
|
|
break;
|
|
}
|
|
}
|
|
if (!blockType) {
|
|
return;
|
|
}
|
|
|
|
// Create the preview block.
|
|
var previewBlock = previewWorkspace.newBlock(blockType);
|
|
previewBlock.initSvg();
|
|
previewBlock.render();
|
|
previewBlock.setMovable(false);
|
|
previewBlock.setDeletable(false);
|
|
previewBlock.moveBy(15, 10);
|
|
previewWorkspace.clearUndo();
|
|
|
|
updateGenerator(previewBlock);
|
|
} finally {
|
|
Blockly.Blocks = backupBlocks;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Inject code into a pre tag, with syntax highlighting.
|
|
* Safe from HTML/script injection.
|
|
* @param {string} code Lines of code.
|
|
* @param {string} id ID of <pre> element to inject into.
|
|
*/
|
|
function injectCode(code, id) {
|
|
var pre = document.getElementById(id);
|
|
pre.textContent = code;
|
|
// Remove the 'prettyprinted' class, so that Prettify will recalculate.
|
|
pre.className = pre.className.replace('prettyprinted', '');
|
|
PR.prettyPrint();
|
|
}
|
|
|
|
/**
|
|
* Return the uneditable container block that everything else attaches to.
|
|
* @return {Blockly.Block}
|
|
*/
|
|
function getRootBlock() {
|
|
var blocks = mainWorkspace.getTopBlocks(false);
|
|
for (var i = 0, block; block = blocks[i]; i++) {
|
|
if (block.type == 'factory_base') {
|
|
return block;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Disable the link button if the format is 'Manual', enable otherwise.
|
|
*/
|
|
function disableEnableLink() {
|
|
var linkButton = document.getElementById('linkButton');
|
|
linkButton.disabled = document.getElementById('format').value == 'Manual';
|
|
}
|
|
|
|
/**
|
|
* Initialize Blockly and layout. Called on page load.
|
|
*/
|
|
function init() {
|
|
if ('BlocklyStorage' in window) {
|
|
BlocklyStorage.HTTPREQUEST_ERROR =
|
|
'There was a problem with the request.\n';
|
|
BlocklyStorage.LINK_ALERT =
|
|
'Share your blocks with this link:\n\n%1';
|
|
BlocklyStorage.HASH_ERROR =
|
|
'Sorry, "%1" doesn\'t correspond with any saved Blockly file.';
|
|
BlocklyStorage.XML_ERROR = 'Could not load your saved file.\n'+
|
|
'Perhaps it was created with a different version of Blockly?';
|
|
var linkButton = document.getElementById('linkButton');
|
|
linkButton.style.display = 'inline-block';
|
|
linkButton.addEventListener('click',
|
|
function() {BlocklyStorage.link(mainWorkspace);});
|
|
disableEnableLink();
|
|
}
|
|
|
|
document.getElementById('helpButton').addEventListener('click',
|
|
function() {
|
|
open('https://developers.google.com/blockly/guides/create-custom-blocks/block-factory',
|
|
'BlockFactoryHelp');
|
|
});
|
|
|
|
var expandList = [
|
|
document.getElementById('blockly'),
|
|
document.getElementById('blocklyMask'),
|
|
document.getElementById('preview'),
|
|
document.getElementById('languagePre'),
|
|
document.getElementById('languageTA'),
|
|
document.getElementById('generatorPre')
|
|
];
|
|
var onresize = function(e) {
|
|
for (var i = 0, expand; expand = expandList[i]; i++) {
|
|
expand.style.width = (expand.parentNode.offsetWidth - 2) + 'px';
|
|
expand.style.height = (expand.parentNode.offsetHeight - 2) + 'px';
|
|
}
|
|
};
|
|
onresize();
|
|
window.addEventListener('resize', onresize);
|
|
|
|
var toolbox = document.getElementById('toolbox');
|
|
mainWorkspace = Blockly.inject('blockly',
|
|
{collapse: false,
|
|
toolbox: toolbox,
|
|
media: '../../media/'});
|
|
|
|
// Create the root block.
|
|
if ('BlocklyStorage' in window && window.location.hash.length > 1) {
|
|
BlocklyStorage.retrieveXml(window.location.hash.substring(1),
|
|
mainWorkspace);
|
|
} else {
|
|
var xml = '<xml xmlns="https://developers.google.com/blockly/xml"><block type="factory_base" deletable="false" movable="false"></block></xml>';
|
|
Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), mainWorkspace);
|
|
}
|
|
mainWorkspace.clearUndo();
|
|
|
|
mainWorkspace.addChangeListener(Blockly.Events.disableOrphans);
|
|
mainWorkspace.addChangeListener(updateLanguage);
|
|
document.getElementById('direction')
|
|
.addEventListener('change', updatePreview);
|
|
document.getElementById('languageTA')
|
|
.addEventListener('change', updatePreview);
|
|
document.getElementById('languageTA')
|
|
.addEventListener('keyup', updatePreview);
|
|
document.getElementById('format')
|
|
.addEventListener('change', formatChange);
|
|
document.getElementById('language')
|
|
.addEventListener('change', updatePreview);
|
|
}
|
|
window.addEventListener('load', init);
|