mirror of
https://github.com/google/blockly.git
synced 2025-12-16 06:10:12 +01:00
* refactor(xml): Move textToDom to core/utils/xml.ts This function being in core/xml.ts was the cause for the last remaining circular import in core/ (between variables.ts and xml.ts). Moving it to utils/xml.ts makes sense anyway, since there is nothing Blockly-specific about this function. Fixes #6817. * fix(closure): Reenable goog.declareModuleId multiple-call check Reenable an assertion which check to make sure that goog.declareModuleId is not called more than once in a module (and which also catches circular imports amongst ES modules, which are not detected by closure-make-deps). * chore(tests,demos): Augo-migrate use of textToDom Testing the migration file entry by auto-migrating all uses of Blockly.Xml.textToDom to Blockly.utils.xml.textToDom. * chore(blocks): Manually migrate remaining use of textToDom Update the one remaining call to textToDom (in blocks/lists.ts) to the function's new location - also removing the last use of the Blockly.Xml / core/xml.ts) module from this file. * docs(xml): Remove unneeded @alias per comments on PR #6818 * fix(imports): Remove unused import
820 lines
26 KiB
JavaScript
820 lines
26 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2012 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @fileoverview JavaScript for Blockly's Block Factory application.
|
|
*/
|
|
'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.common.getMainWorkspace().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.utils.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);
|