mirror of
https://github.com/google/blockly.git
synced 2026-01-07 00:50:27 +01:00
* refactor(tests): Move and rename prepare.js, blockly.mjs
Since prepare.js and blockly.mjs are going to be needed for running
all tests in uncompiled mode (not just the playgrounds), move them
tests/. Further, rename prepare.js to bootstrap.js to better reflect
its purpose.
* feat(tests): Introduce BLOCKLY_BOOTSTRAP_OPTIONS
Provide a mechanism for web pages that use bootstrap.js to control
what is loaded and how.
* fix(tests): Use the blockly repository path for all script src= URLs
Previously the (non-advanced) playground was only correctly loadging
on localhost because you can put an arbitrary number of "../"s in front
of a relative URL and it just takes you to the root directory.
* fix(tests): Don't use template literals in bootstrap.js
This is necessary (but not necessarily sufficient) to be able to
load the file in IE 11.
* fix(tests): Throw error if attempting to bootstrap in node.js
* feat(tests): Make bootstrap.js more configurable.
* Terminology change: use "compressed" and "uncompressed" to describe
what Closure Compiler calls "compiled" and "uncompiled", to reduce
confusion with the compilation that will be done by tsc.
* Get the list of modules to bootstrap (in compressed mode), or
scripts to load (in compressed mode) from BLOCKLY_BOOTSTRAP_OPTIONS,
to allow calling scripts to to specify exactly what to load.
* feat(tests): Use a proper quote function
We need to generate string literals. Best to use a quote function
instead of concatenating on quote marks withou escaping. Copy a
well-tested one from Code City.
* feat(tests): Support an additionalScripts option
This is a list of scripts to load (in order) once the required modules
have been bootstrapped.
We do this using goog.addDependency to make the first script depend
on the required modules, then each subsequent script depend on the
previous one, and then finally goog.bootstrapping the last such script.
* refactor(tests): Remove special handling of msg/messages.js
* refactor(tests): Use additionalScripts for all script loading
Use additionalScripts option for all script loading in
playground.html and advanced_playground.html.
* refactor(tests): Use bootstrap instead of uncompressed in Mocha tests
Use tests/bootstrap.js instead of blockly_uncompressed.js to load
blockly in uncompressed mode in the Mocha tests.
This entails adding a new item, despFiles, to BLOCKLY_BOOTSTRAP_OPTIONS,
to allow tests/deps.mocha.js to be loaded at the appropriate point.
Mention of blockly_uncompressed.js is removed from
tests/mocah/.mocharc.js; it's not clear to me what effect the "file:"
directive in this file might have previously had and I was not able to
find documentation for it on mochajs.org, but in any case removing it
appears to have had no ill effect.
* refactor(tests): Use bootstrap instead of uncompressed in generator tests
This entails adding an additional check in bootstrap so as to load
uncompressed when loading from a file: URL, since these are not
localhost URLs - though in fact the generator tests run equally well
in compressed mode, albeit against (for now) the previously-check-in
build products rather than the live code.
* refactor(test): Use bootstrap.js in multi_playground.html
This removes the last use of load_all.js, so remove it.
* chore(tests): Delete blockly_uncompressed.js
Its function has now been entirely subsumed by tests/bootstrap.js,
so remove it and update any remaining mentions of it.
Also fix formatting and positions of some comments in playground.html.
* fix(tests): Rewrite bootstrap sequencing code
An earlier commit modified the generated <script> to use
goog.addDependency to trick the debug module loader into loading
.additionalScripts (via goog.bootstrap), but it turns out there is
a small problem: scripts like msg/messages.js have undeclared
dependencies on the Blockly module, and without a call to
goog.require('Blockly') in them they can end up being run before
the Blockly module is fully loaded.
(This problem only occurs when there are ES Modules, rather than
merely goog.modules, in the mix.)
Fix this by adding a script, bootstrap_helper.js, to be loaded
options.requires and any options.additionalScripts that makes an
explicit call to goog.require for each of option.requires.
Also refactor the code so that instead of generating a loop which
calls goog.addDependency, we generate the addDependency calls
directly. This makes debugging a bit easer as we can use the browser's
dev tools to inspect the generated calls in the DOM tree.
* fix(tests): Prevent spurious transpilation warnings
For some reason when the debug module loader encounters ES modules
it starts to complain about being unable to transpile some ES202x
features in other (non-ESM) modules, even though it doesn't normally
try to transpile those.
Since uncompressed-mode testing is almost exclusively on modern
browsers we don't care about transpiling these features, so suppress
the warnings instead.
* refactor(tests): Rename blockly.mjs to bootstrap_done.mjs; simplify
Since blockly.mjs is no longer returning just the exports object
from core/blockly.js (see PR #5995), it might be better named after
its actual purpose: to wait for bootstrapping to be done.
Remove all the code that was used to pass the blockly.js exports
object along from the bootstrap callback to the blockly.mjs export,
since there's no reason to go to a lot of trouble to set a local
variable named Blockly to the same value as a global variable named
Blockly.
(Something like this may be needed again in future, but certainly in
a different form.)
* chore(tests): Use freshly-build files in compressed mode.
Use the freshly-built build/*_compresssed.js files when bootstrapping
in compressed mode, rather than using the checked-in files in the
repository root.
This helps ensure that compressed and uncompressed mode will be
testing (as closely as possible) the same code.
* chore(tests): Rename BlocklyLoader to blocklyLoader; record compressed
- Rename the BlocklyLoader global to blocklyLoader (since it is not
a class constructor).
- Create it regardless of whether we are bootstrapping in
uncompressed or loading compressed via <script> tags.
- Record which we are doing as .compressed, and use this property
to choose playground background colour.
* chore(tests): Resolve comments for PR #6214
Mostly documentation changes, but notably renaming blocklyLoader to
bootstrapInfo.
* Revert "chore(tests): Use freshly-build files in compressed mode."
This reverts commit de8d356838.
1255 lines
37 KiB
HTML
1255 lines
37 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Blockly Playground</title>
|
|
|
|
<!-- This script loads uncompressed when on localhost and loads
|
|
compressed when it is being hosted or on Internet Explorer. -->
|
|
<script>
|
|
var BLOCKLY_BOOTSTRAP_OPTIONS = {
|
|
additionalScripts: [
|
|
'msg/messages.js',
|
|
'tests/playgrounds/screenshot.js',
|
|
'node_modules/@blockly/dev-tools/dist/index.js',
|
|
],
|
|
}
|
|
</script>
|
|
<script src="bootstrap.js"></script>
|
|
<script type=module>
|
|
// Wait for Blockly to finish loading.
|
|
import './bootstrap_done.mjs';
|
|
|
|
const IS_UNCOMPRESSED = !window.bootstrapInfo.compressed; // See bootstrap.js
|
|
var workspace = null;
|
|
|
|
function start() {
|
|
setBackgroundColour();
|
|
|
|
// Parse the URL arguments.
|
|
var match = location.search.match(/dir=([^&]+)/);
|
|
var rtl = match && match[1] == 'rtl';
|
|
document.forms.options.elements.dir.selectedIndex = Number(rtl);
|
|
var toolbox = getToolboxElement();
|
|
setToolboxDropdown();
|
|
match = location.search.match(/side=([^&]+)/);
|
|
var autoimport = !!location.search.match(/autoimport=([^&]+)/);
|
|
// Create main workspace.
|
|
workspace = Blockly.inject('blocklyDiv',
|
|
{
|
|
comments: true,
|
|
collapse: true,
|
|
disable: true,
|
|
grid:
|
|
{
|
|
spacing: 25,
|
|
length: 3,
|
|
colour: '#ccc',
|
|
snap: true
|
|
},
|
|
horizontalLayout: false,
|
|
maxBlocks: Infinity,
|
|
maxInstances: {'test_basic_limit_instances': 3},
|
|
maxTrashcanContents: 256,
|
|
media: '../media/',
|
|
oneBasedIndex: true,
|
|
readOnly: false,
|
|
rtl: rtl,
|
|
move: {
|
|
scrollbars: true,
|
|
drag: true,
|
|
wheel: false,
|
|
},
|
|
toolbox: toolbox,
|
|
toolboxPosition: 'start',
|
|
renderer: 'geras',
|
|
zoom:
|
|
{
|
|
controls: true,
|
|
wheel: true,
|
|
startScale: 1.0,
|
|
maxScale: 4,
|
|
minScale: 0.25,
|
|
scaleSpeed: 1.1
|
|
}
|
|
});
|
|
initToolbox(workspace);
|
|
workspace.configureContextMenu = configureContextMenu;
|
|
// Restore previously displayed text.
|
|
if (sessionStorage) {
|
|
var text = sessionStorage.getItem('textarea');
|
|
if (text) {
|
|
document.getElementById('importExport').value = text;
|
|
}
|
|
// Restore event logging state.
|
|
var logMainEventsState = sessionStorage.getItem('logEvents');
|
|
logEvents(Boolean(Number(logMainEventsState)));
|
|
var logToolboxFlyoutEventsState = sessionStorage.getItem('logFlyoutEvents');
|
|
logFlyoutEvents(Boolean(Number(logToolboxFlyoutEventsState)));
|
|
} else {
|
|
// MSIE 11 does not support sessionStorage on file:// URLs.
|
|
logEvents(false);
|
|
}
|
|
taChange();
|
|
if (autoimport) {
|
|
load();
|
|
}
|
|
|
|
addEventHandlers();
|
|
}
|
|
|
|
function setBackgroundColour() {
|
|
// Set background colour to differentiate between compressed and uncompressed mode.
|
|
if (IS_UNCOMPRESSED) {
|
|
document.body.style.backgroundColor = '#d6d6ff'; // Familiar lilac.
|
|
} else {
|
|
document.body.style.backgroundColor = '#60fcfc'; // Unfamiliar blue.
|
|
}
|
|
}
|
|
|
|
function getToolboxSuffix() {
|
|
var match = location.search.match(/toolbox=([^&]+)/);
|
|
// Default to the basic toolbox with categories and untyped variables,
|
|
// but override that if the toolbox type is set in the URL.
|
|
return (match ? match[1] : 'categories');
|
|
}
|
|
|
|
function getToolboxElement() {
|
|
var toolboxSuffix = getToolboxSuffix();
|
|
if (toolboxSuffix == 'test-blocks') {
|
|
if (typeof window.toolboxTestBlocks !== 'undefined') {
|
|
return toolboxTestBlocks;
|
|
} else {
|
|
alert('You need to run \'npm install\' in order to use the test blocks.');
|
|
toolboxSuffix = 'categories';
|
|
}
|
|
}
|
|
// The three possible values are: "simple", "categories",
|
|
// "categories-typed-variables".
|
|
return document.getElementById('toolbox-' + toolboxSuffix);
|
|
}
|
|
|
|
function setToolboxDropdown() {
|
|
var toolboxNames = [
|
|
'toolbox-categories',
|
|
'toolbox-categories-typed-variables',
|
|
'toolbox-simple',
|
|
'toolbox-test-blocks'
|
|
];
|
|
var toolboxSuffix = getToolboxSuffix();
|
|
document.forms.options.elements.toolbox.selectedIndex =
|
|
toolboxNames.indexOf('toolbox-' + toolboxSuffix);
|
|
}
|
|
|
|
function initToolbox(workspace) {
|
|
var toolboxSuffix = getToolboxSuffix();
|
|
if (toolboxSuffix == 'test-blocks' &&
|
|
typeof window.toolboxTestBlocksInit !== 'undefined') {
|
|
toolboxTestBlocksInit(workspace);
|
|
}
|
|
}
|
|
|
|
function saveXml() {
|
|
var output = document.getElementById('importExport');
|
|
var xml = Blockly.Xml.workspaceToDom(workspace);
|
|
output.value = Blockly.Xml.domToPrettyText(xml);
|
|
output.focus();
|
|
output.select();
|
|
taChange();
|
|
}
|
|
|
|
function saveJson() {
|
|
var output = document.getElementById('importExport');
|
|
var state = Blockly.serialization.workspaces.save(workspace);
|
|
output.value = JSON.stringify(state, null, 2);
|
|
output.focus();
|
|
output.select();
|
|
taChange();
|
|
}
|
|
|
|
function load() {
|
|
var input = document.getElementById('importExport');
|
|
if (!input.value) {
|
|
return;
|
|
}
|
|
var valid = saveIsValid(input.value);
|
|
if (valid.json) {
|
|
var state = JSON.parse(input.value);
|
|
Blockly.serialization.workspaces.load(state, workspace);
|
|
} else if (valid.xml) {
|
|
var xml = Blockly.Xml.textToDom(input.value);
|
|
Blockly.Xml.domToWorkspace(xml, workspace);
|
|
}
|
|
taChange();
|
|
}
|
|
|
|
function toCode(lang) {
|
|
var output = document.getElementById('importExport');
|
|
output.value = Blockly[lang].workspaceToCode(workspace);
|
|
taChange();
|
|
}
|
|
|
|
// Disable the "Load" button if the save state is invalid.
|
|
// Preserve text between page reloads.
|
|
function taChange() {
|
|
var textarea = document.getElementById('importExport');
|
|
if (sessionStorage) {
|
|
sessionStorage.setItem('textarea', textarea.value);
|
|
}
|
|
var valid = saveIsValid(textarea.value);
|
|
document.getElementById('import').disabled = !valid.json && !valid.xml;
|
|
}
|
|
|
|
function saveIsValid(save) {
|
|
var validJson = true;
|
|
try {
|
|
JSON.parse(save);
|
|
} catch (e) {
|
|
validJson = false;
|
|
}
|
|
var validXml = true
|
|
try {
|
|
Blockly.Xml.textToDom(save);
|
|
} catch (e) {
|
|
validXml = false;
|
|
}
|
|
return {
|
|
json: validJson,
|
|
xml: validXml
|
|
}
|
|
}
|
|
|
|
function logEvents(state) {
|
|
var checkbox = document.getElementById('logCheck');
|
|
checkbox.checked = state;
|
|
if (sessionStorage) {
|
|
sessionStorage.setItem('logEvents', Number(state));
|
|
}
|
|
if (state) {
|
|
workspace.addChangeListener(logger);
|
|
} else {
|
|
workspace.removeChangeListener(logger);
|
|
}
|
|
}
|
|
|
|
function logFlyoutEvents(state) {
|
|
var checkbox = document.getElementById('logFlyoutCheck');
|
|
checkbox.checked = state;
|
|
if (sessionStorage) {
|
|
sessionStorage.setItem('logFlyoutEvents', Number(state));
|
|
}
|
|
var flyoutWorkspace = workspace.getFlyout().getWorkspace();
|
|
if (state) {
|
|
flyoutWorkspace.addChangeListener(logger);
|
|
} else {
|
|
flyoutWorkspace.removeChangeListener(logger);
|
|
}
|
|
}
|
|
|
|
function configureContextMenu(menuOptions, e) {
|
|
var screenshotOption = {
|
|
text: 'Download Screenshot',
|
|
enabled: workspace.getTopBlocks().length,
|
|
callback: function() {
|
|
downloadScreenshot(workspace);
|
|
}
|
|
};
|
|
menuOptions.push(screenshotOption);
|
|
|
|
// Adds a default-sized workspace comment to the workspace.
|
|
menuOptions.push(Blockly.ContextMenu.workspaceCommentOption(workspace, e));
|
|
}
|
|
|
|
function logger(e) {
|
|
console.log(e);
|
|
}
|
|
|
|
function airstrike(n) {
|
|
var prototypes = [];
|
|
var toolbox = getToolboxElement();
|
|
var blocks = toolbox.getElementsByTagName('block');
|
|
for (var i = 0, block; block = blocks[i]; i++) {
|
|
prototypes.push(block.getAttribute('type'));
|
|
}
|
|
for (var i = 0; i < n; i++) {
|
|
var prototype = prototypes[Math.floor(Math.random() * prototypes.length)];
|
|
var block = workspace.newBlock(prototype);
|
|
block.initSvg();
|
|
block.getSvgRoot().setAttribute('transform', 'translate(' +
|
|
Math.round(Math.random() * 450 + 40) + ', ' +
|
|
Math.round(Math.random() * 600 + 40) + ')');
|
|
block.render();
|
|
}
|
|
}
|
|
|
|
function spaghetti(n) {
|
|
var xml = spaghettiXml;
|
|
for(var i = 0; i < n; i++) {
|
|
xml = xml.replace(/(<(statement|next)( name="DO0")?>)<\//g,
|
|
'$1' + spaghettiXml + '</');
|
|
}
|
|
xml = '<xml xmlns="https://developers.google.com/blockly/xml">' + xml + '</xml>';
|
|
var dom = Blockly.Xml.textToDom(xml);
|
|
console.time('Spaghetti domToWorkspace');
|
|
Blockly.Xml.domToWorkspace(dom, workspace);
|
|
console.timeEnd('Spaghetti domToWorkspace');
|
|
}
|
|
var spaghettiXml = [
|
|
' <block type="controls_if">',
|
|
' <value name="IF0">',
|
|
' <block type="logic_compare">',
|
|
' <field name="OP">EQ</field>',
|
|
' <value name="A">',
|
|
' <block type="math_arithmetic">',
|
|
' <field name="OP">MULTIPLY</field>',
|
|
' <value name="A">',
|
|
' <block type="math_number">',
|
|
' <field name="NUM">6</field>',
|
|
' </block>',
|
|
' </value>',
|
|
' <value name="B">',
|
|
' <block type="math_number">',
|
|
' <field name="NUM">7</field>',
|
|
' </block>',
|
|
' </value>',
|
|
' </block>',
|
|
' </value>',
|
|
' <value name="B">',
|
|
' <block type="math_number">',
|
|
' <field name="NUM">42</field>',
|
|
' </block>',
|
|
' </value>',
|
|
' </block>',
|
|
' </value>',
|
|
' <statement name="DO0"></statement>',
|
|
' <next></next>',
|
|
' </block>'].join('\n');
|
|
|
|
function jsoSpaghetti(n) {
|
|
var str = spaghettiJs;
|
|
for (var i = 0; i < n; i++) {
|
|
str = str.replace(/{}/g, `{"block":${spaghettiJs}}`);
|
|
}
|
|
var obj = {
|
|
'blocks': {
|
|
'blocks': [
|
|
JSON.parse(str)
|
|
]
|
|
}
|
|
};
|
|
console.time('Spaghetti serialization');
|
|
Blockly.serialization.workspaces.load(obj, workspace);
|
|
console.timeEnd('Spaghetti serialization');
|
|
}
|
|
var spaghettiJs = JSON.stringify({
|
|
'type': 'controls_if',
|
|
'inputs': {
|
|
'IF0': {
|
|
'block': {
|
|
'type': 'logic_compare',
|
|
'fields': {
|
|
'OP': 'EQ',
|
|
},
|
|
'inputs': {
|
|
'A': {
|
|
'block': {
|
|
'type': 'math_arithmetic',
|
|
'fields': {
|
|
'OP': 'MULTIPLY',
|
|
},
|
|
'inputs': {
|
|
'A': {
|
|
'block': {
|
|
'type': 'math_number',
|
|
'fields': {
|
|
'NUM': 6
|
|
}
|
|
}
|
|
},
|
|
'B': {
|
|
'block': {
|
|
'type': 'math_number',
|
|
'fields': {
|
|
'NUM': 7
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
'B': {
|
|
'block': {
|
|
'type': 'math_number',
|
|
'fields': {
|
|
'NUM': 42
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
'DO0': { }
|
|
},
|
|
'next': { }
|
|
});
|
|
|
|
function addEventHandlers() {
|
|
document.getElementById('save-json').addEventListener('click', saveJson);
|
|
document.getElementById('save-xml').addEventListener('click', saveXml);
|
|
document.getElementById('import').addEventListener('click', load);
|
|
|
|
document.getElementById('to-code-js')
|
|
.addEventListener('click', () => toCode('JavaScript'));
|
|
document.getElementById('to-code-py')
|
|
.addEventListener('click', () => toCode('Python'));
|
|
document.getElementById('to-code-php')
|
|
.addEventListener('click', () => toCode('PHP'));
|
|
document.getElementById('to-code-lua')
|
|
.addEventListener('click', () => toCode('Lua'));
|
|
document.getElementById('to-code-dart')
|
|
.addEventListener('click', () => toCode('Dart'));
|
|
|
|
document.getElementById('airstrike')
|
|
.addEventListener('click', () => airstrike(100));
|
|
document.getElementById('spaghetti-xml')
|
|
.addEventListener('click', () => spaghetti(8));
|
|
document.getElementById('spaghetti-js')
|
|
.addEventListener('click', () => jsoSpaghetti(8));
|
|
|
|
document.getElementById('logCheck')
|
|
.addEventListener('click', function() { logEvents(this.checked) });
|
|
document.getElementById('logFlyoutCheck')
|
|
.addEventListener('click', function() { logFlyoutEvents(this.checked) });
|
|
|
|
document.getElementById('importExport').addEventListener('change', taChange);
|
|
document.getElementById('importExport').addEventListener('keyup', taChange);
|
|
|
|
document.getElementById('show')
|
|
.addEventListener('click', function() { workspace.setVisible(true); });
|
|
document.getElementById('hide')
|
|
.addEventListener('click', function() { workspace.setVisible(false); });
|
|
}
|
|
|
|
// Call start(). Because this <script> has type=module, it is
|
|
// automatically deferred, so it will not be run until after the
|
|
// document has been parsed, but before firing DOMContentLoaded.
|
|
start();
|
|
|
|
</script>
|
|
|
|
<style>
|
|
html, body {
|
|
height: 100%;
|
|
}
|
|
body {
|
|
background-color: #fff;
|
|
font-family: sans-serif;
|
|
overflow: hidden;
|
|
}
|
|
h1 {
|
|
font-weight: normal;
|
|
font-size: 140%;
|
|
}
|
|
#blocklyDiv {
|
|
float: right;
|
|
height: 95%;
|
|
width: 70%;
|
|
}
|
|
#importExport {
|
|
font-family: monospace;
|
|
}
|
|
|
|
.ioLabel>.blocklyFlyoutLabelText {
|
|
font-style: italic;
|
|
}
|
|
|
|
#blocklyDiv.renderingDebug .blockRenderDebug {
|
|
display: block;
|
|
}
|
|
|
|
.playgroundToggleOptions {
|
|
list-style: none;
|
|
padding: 0;
|
|
}
|
|
.playgroundToggleOptions li {
|
|
margin-top: 1em;
|
|
}
|
|
|
|
.zelos-renderer .blocklyFlyoutButton .blocklyText {
|
|
font-size: 1.5rem;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div id="blocklyDiv"></div>
|
|
|
|
<h1>Blockly Playground</h1>
|
|
|
|
<p>
|
|
<input id="show" type="button" value="Show"></input> -
|
|
<input id="hide" type="button" value="Hide"></input> -
|
|
<a href="playgrounds/advanced_playground.html">Advanced</a>
|
|
</p>
|
|
|
|
<form id="options">
|
|
<select name="dir" onchange="document.forms.options.submit()">
|
|
<option value="ltr">LTR</option>
|
|
<option value="rtl">RTL</option>
|
|
</select>
|
|
<select name="toolbox" onchange="document.forms.options.submit()">
|
|
<option value="categories">Categories (untyped variables)</option>
|
|
<option value="categories-typed-variables">Categories (typed variables)</option>
|
|
<option value="simple">Simple</option>
|
|
<option value="test-blocks">Test Blocks</option>
|
|
</select>
|
|
</form>
|
|
<p>
|
|
<input id="save-json" type="button" value="Save JSON">
|
|
<input id="save-xml" type="button" value="Save XML">
|
|
<input type="button" value="Load" id="import">
|
|
<br>
|
|
<input id="to-code-js" type="button" value="To JavaScript">
|
|
<input id="to-code-py" type="button" value="To Python">
|
|
<input id="to-code-php" type="button" value="To PHP">
|
|
<input id="to-code-lua" type="button" value="To Lua">
|
|
<input id="to-code-dart" type="button" value="To Dart">
|
|
<br>
|
|
<textarea id="importExport" style="width: 26%; height: 12em"></textarea>
|
|
</p>
|
|
|
|
<p>
|
|
Stress test:
|
|
<input id="airstrike" type="button" value="Airstrike!">
|
|
<input id="spaghetti-xml" type="button" value="Spaghetti!">
|
|
<input id="spaghetti-js" type="button" value="JS Spaghetti!">
|
|
</p>
|
|
<ul class="playgroundToggleOptions">
|
|
<li>
|
|
<label for="logCheck">Log main workspace events:</label>
|
|
<input type="checkbox" id="logCheck">
|
|
</li>
|
|
<li>
|
|
<label for="logFlyoutCheck">Log flyout events:</label>
|
|
<input type="checkbox" id="logFlyoutCheck">
|
|
</li>
|
|
</ul>
|
|
|
|
|
|
<!-- The next three blocks of XML are sample toolboxes for testing basic
|
|
configurations. For more information on building toolboxes, see https://developers.google.com/blockly/guides/configure/web/toolbox -->
|
|
|
|
<!-- toolbox-simple is an always-open flyout with no category menu.
|
|
Always-open flyouts are a good idea if you have a small number of blocks. -->
|
|
<xml xmlns="https://developers.google.com/blockly/xml" id="toolbox-simple" style="display: none">
|
|
<block type="controls_ifelse"></block>
|
|
<block type="logic_compare"></block>
|
|
<!-- <block type="control_repeat"></block> -->
|
|
<block type="logic_operation"></block>
|
|
<block type="controls_repeat_ext">
|
|
<value name="TIMES">
|
|
<shadow type="math_number">
|
|
<field name="NUM">10</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="logic_operation"></block>
|
|
<block type="logic_negate"></block>
|
|
<block type="logic_boolean"></block>
|
|
<block type="logic_null" disabled="true"></block>
|
|
<block type="logic_ternary"></block>
|
|
<block type="text_charAt">
|
|
<value name="VALUE">
|
|
<block type="variables_get">
|
|
<field name="VAR">text</field>
|
|
</block>
|
|
</value>
|
|
</block>
|
|
</xml>
|
|
|
|
<!-- toolbox-categories has a category menu and an auto-closing flyout. The
|
|
Variables category uses untyped variable blocks.
|
|
See https://developers.google.com/blockly/guides/create-custom-blocks/variables#untyped_variable_blocks for more information. -->
|
|
<xml xmlns="https://developers.google.com/blockly/xml" id="toolbox-categories" style="display: none">
|
|
<category name="Logic" categorystyle="logic_category">
|
|
<block type="controls_if"></block>
|
|
<block type="logic_compare"></block>
|
|
<block type="logic_operation"></block>
|
|
<block type="logic_negate"></block>
|
|
<block type="logic_boolean"></block>
|
|
<block type="logic_null" disabled="true"></block>
|
|
<block type="logic_ternary"></block>
|
|
</category>
|
|
<category name="Loops" categorystyle="loop_category">
|
|
<block type="controls_repeat_ext">
|
|
<value name="TIMES">
|
|
<shadow type="math_number">
|
|
<field name="NUM">10</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="controls_repeat" disabled="true"></block>
|
|
<block type="controls_whileUntil"></block>
|
|
<block type="controls_for">
|
|
<value name="FROM">
|
|
<shadow type="math_number">
|
|
<field name="NUM">1</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="TO">
|
|
<shadow type="math_number">
|
|
<field name="NUM">10</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="BY">
|
|
<shadow type="math_number">
|
|
<field name="NUM">1</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="controls_forEach"></block>
|
|
<block type="controls_flow_statements"></block>
|
|
</category>
|
|
<category name="Math" categorystyle="math_category">
|
|
<block type="math_number" gap="32">
|
|
<field name="NUM">123</field>
|
|
</block>
|
|
<block type="math_arithmetic">
|
|
<value name="A">
|
|
<shadow type="math_number">
|
|
<field name="NUM">1</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="B">
|
|
<shadow type="math_number">
|
|
<field name="NUM">1</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="math_single">
|
|
<value name="NUM">
|
|
<shadow type="math_number">
|
|
<field name="NUM">9</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="math_trig">
|
|
<value name="NUM">
|
|
<shadow type="math_number">
|
|
<field name="NUM">45</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="math_constant"></block>
|
|
<block type="math_number_property">
|
|
<value name="NUMBER_TO_CHECK">
|
|
<shadow type="math_number">
|
|
<field name="NUM">0</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="math_round">
|
|
<value name="NUM">
|
|
<shadow type="math_number">
|
|
<field name="NUM">3.1</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="math_on_list"></block>
|
|
<block type="math_modulo">
|
|
<value name="DIVIDEND">
|
|
<shadow type="math_number">
|
|
<field name="NUM">64</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="DIVISOR">
|
|
<shadow type="math_number">
|
|
<field name="NUM">10</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="math_constrain">
|
|
<value name="VALUE">
|
|
<shadow type="math_number">
|
|
<field name="NUM">50</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="LOW">
|
|
<shadow type="math_number">
|
|
<field name="NUM">1</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="HIGH">
|
|
<shadow type="math_number">
|
|
<field name="NUM">100</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="math_random_int">
|
|
<value name="FROM">
|
|
<shadow type="math_number">
|
|
<field name="NUM">1</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="TO">
|
|
<shadow type="math_number">
|
|
<field name="NUM">100</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="math_random_float"></block>
|
|
<block type="math_atan2">
|
|
<value name="X">
|
|
<shadow type="math_number">
|
|
<field name="NUM">1</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="Y">
|
|
<shadow type="math_number">
|
|
<field name="NUM">1</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
</category>
|
|
<category name="Text" categorystyle="text_category">
|
|
<block type="text"></block>
|
|
<block type="text_multiline"></block>
|
|
<block type="text_join"></block>
|
|
<block type="text_append">
|
|
<value name="TEXT">
|
|
<shadow type="text"></shadow>
|
|
</value>
|
|
</block>
|
|
<block type="text_length">
|
|
<value name="VALUE">
|
|
<shadow type="text">
|
|
<field name="TEXT">abc</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="text_isEmpty">
|
|
<value name="VALUE">
|
|
<shadow type="text">
|
|
<field name="TEXT"></field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="text_indexOf">
|
|
<value name="VALUE">
|
|
<block type="variables_get">
|
|
<field name="VAR">text</field>
|
|
</block>
|
|
</value>
|
|
<value name="FIND">
|
|
<shadow type="text">
|
|
<field name="TEXT">abc</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="text_charAt">
|
|
<value name="VALUE">
|
|
<block type="variables_get">
|
|
<field name="VAR">text</field>
|
|
</block>
|
|
</value>
|
|
</block>
|
|
<block type="text_getSubstring">
|
|
<value name="STRING">
|
|
<block type="variables_get">
|
|
<field name="VAR">text</field>
|
|
</block>
|
|
</value>
|
|
</block>
|
|
<block type="text_changeCase">
|
|
<value name="TEXT">
|
|
<shadow type="text">
|
|
<field name="TEXT">abc</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="text_trim">
|
|
<value name="TEXT">
|
|
<shadow type="text">
|
|
<field name="TEXT">abc</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="text_count">
|
|
<value name="SUB">
|
|
<shadow type="text"></shadow>
|
|
</value>
|
|
<value name="TEXT">
|
|
<shadow type="text"></shadow>
|
|
</value>
|
|
</block>
|
|
<block type="text_replace">
|
|
<value name="FROM">
|
|
<shadow type="text"></shadow>
|
|
</value>
|
|
<value name="TO">
|
|
<shadow type="text"></shadow>
|
|
</value>
|
|
<value name="TEXT">
|
|
<shadow type="text"></shadow>
|
|
</value>
|
|
</block>
|
|
<block type="text_reverse">
|
|
<value name="TEXT">
|
|
<shadow type="text"></shadow>
|
|
</value>
|
|
</block>
|
|
<label text="Input/Output:" web-class="ioLabel"></label>
|
|
<block type="text_print">
|
|
<value name="TEXT">
|
|
<shadow type="text">
|
|
<field name="TEXT">abc</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="text_prompt_ext">
|
|
<value name="TEXT">
|
|
<shadow type="text">
|
|
<field name="TEXT">abc</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
</category>
|
|
<category name="Lists" categorystyle="list_category">
|
|
<block type="lists_create_with">
|
|
<mutation items="0"></mutation>
|
|
</block>
|
|
<block type="lists_create_with"></block>
|
|
<block type="lists_repeat">
|
|
<value name="NUM">
|
|
<shadow type="math_number">
|
|
<field name="NUM">5</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="lists_length"></block>
|
|
<block type="lists_isEmpty"></block>
|
|
<block type="lists_indexOf">
|
|
<value name="VALUE">
|
|
<block type="variables_get">
|
|
<field name="VAR">list</field>
|
|
</block>
|
|
</value>
|
|
</block>
|
|
<block type="lists_getIndex">
|
|
<value name="VALUE">
|
|
<block type="variables_get">
|
|
<field name="VAR">list</field>
|
|
</block>
|
|
</value>
|
|
</block>
|
|
<block type="lists_setIndex">
|
|
<value name="LIST">
|
|
<block type="variables_get">
|
|
<field name="VAR">list</field>
|
|
</block>
|
|
</value>
|
|
</block>
|
|
<block type="lists_getSublist">
|
|
<value name="LIST">
|
|
<block type="variables_get">
|
|
<field name="VAR">list</field>
|
|
</block>
|
|
</value>
|
|
</block>
|
|
<block type="lists_split">
|
|
<value name="DELIM">
|
|
<shadow type="text">
|
|
<field name="TEXT">,</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="lists_sort"></block>
|
|
<block type="lists_reverse"></block>
|
|
</category>
|
|
<category name="Colour" categorystyle="colour_category">
|
|
<block type="colour_picker"></block>
|
|
<block type="colour_random"></block>
|
|
<block type="colour_rgb">
|
|
<value name="RED">
|
|
<shadow type="math_number">
|
|
<field name="NUM">100</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="GREEN">
|
|
<shadow type="math_number">
|
|
<field name="NUM">50</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="BLUE">
|
|
<shadow type="math_number">
|
|
<field name="NUM">0</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="colour_blend">
|
|
<value name="COLOUR1">
|
|
<shadow type="colour_picker">
|
|
<field name="COLOUR">#ff0000</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="COLOUR2">
|
|
<shadow type="colour_picker">
|
|
<field name="COLOUR">#3333ff</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="RATIO">
|
|
<shadow type="math_number">
|
|
<field name="NUM">0.5</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
</category>
|
|
<sep></sep>
|
|
<category name="Variables" categorystyle="variable_category" custom="VARIABLE"></category>
|
|
<category name="Functions" categorystyle="procedure_category" custom="PROCEDURE"></category>
|
|
</xml>
|
|
|
|
<!-- toolbox-categories-typed-variables has a category menu and an
|
|
auto-closing flyout. The Variables category uses typed variable blocks.
|
|
See https://developers.google.com/blockly/guides/create-custom-blocks/variables#typed_variable_blocks for more information. -->
|
|
<xml xmlns="https://developers.google.com/blockly/xml" id="toolbox-categories-typed-variables" style="display: none">
|
|
<category name="Logic" categorystyle="logic_category">
|
|
<block type="controls_if"></block>
|
|
<block type="logic_compare"></block>
|
|
<block type="logic_operation"></block>
|
|
<block type="logic_negate"></block>
|
|
<block type="logic_boolean"></block>
|
|
<block type="logic_null" disabled="true"></block>
|
|
<block type="logic_ternary"></block>
|
|
</category>
|
|
<category name="Loops" categorystyle="loop_category">
|
|
<block type="controls_repeat_ext">
|
|
<value name="TIMES">
|
|
<shadow type="math_number">
|
|
<field name="NUM">10</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="controls_repeat" disabled="true"></block>
|
|
<block type="controls_whileUntil"></block>
|
|
<block type="controls_for">
|
|
<value name="FROM">
|
|
<shadow type="math_number">
|
|
<field name="NUM">1</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="TO">
|
|
<shadow type="math_number">
|
|
<field name="NUM">10</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="BY">
|
|
<shadow type="math_number">
|
|
<field name="NUM">1</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="controls_forEach"></block>
|
|
<block type="controls_flow_statements"></block>
|
|
</category>
|
|
<category name="Math" categorystyle="math_category">
|
|
<block type="math_number" gap="32">
|
|
<field name="NUM">123</field>
|
|
</block>
|
|
<block type="math_arithmetic">
|
|
<value name="A">
|
|
<shadow type="math_number">
|
|
<field name="NUM">1</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="B">
|
|
<shadow type="math_number">
|
|
<field name="NUM">1</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="math_single">
|
|
<value name="NUM">
|
|
<shadow type="math_number">
|
|
<field name="NUM">9</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="math_trig">
|
|
<value name="NUM">
|
|
<shadow type="math_number">
|
|
<field name="NUM">45</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="math_constant"></block>
|
|
<block type="math_number_property">
|
|
<value name="NUMBER_TO_CHECK">
|
|
<shadow type="math_number">
|
|
<field name="NUM">0</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="math_round">
|
|
<value name="NUM">
|
|
<shadow type="math_number">
|
|
<field name="NUM">3.1</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="math_on_list"></block>
|
|
<block type="math_modulo">
|
|
<value name="DIVIDEND">
|
|
<shadow type="math_number">
|
|
<field name="NUM">64</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="DIVISOR">
|
|
<shadow type="math_number">
|
|
<field name="NUM">10</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="math_constrain">
|
|
<value name="VALUE">
|
|
<shadow type="math_number">
|
|
<field name="NUM">50</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="LOW">
|
|
<shadow type="math_number">
|
|
<field name="NUM">1</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="HIGH">
|
|
<shadow type="math_number">
|
|
<field name="NUM">100</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="math_random_int">
|
|
<value name="FROM">
|
|
<shadow type="math_number">
|
|
<field name="NUM">1</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="TO">
|
|
<shadow type="math_number">
|
|
<field name="NUM">100</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="math_random_float"></block>
|
|
<block type="math_atan2">
|
|
<value name="X">
|
|
<shadow type="math_number">
|
|
<field name="NUM">1</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="Y">
|
|
<shadow type="math_number">
|
|
<field name="NUM">1</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
</category>
|
|
<category name="Text" categorystyle="text_category">
|
|
<block type="text"></block>
|
|
<block type="text_multiline"></block>
|
|
<block type="text_join"></block>
|
|
<block type="text_append">
|
|
<value name="TEXT">
|
|
<shadow type="text"></shadow>
|
|
</value>
|
|
</block>
|
|
<block type="text_length">
|
|
<value name="VALUE">
|
|
<shadow type="text">
|
|
<field name="TEXT">abc</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="text_isEmpty">
|
|
<value name="VALUE">
|
|
<shadow type="text">
|
|
<field name="TEXT"></field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="text_indexOf">
|
|
<value name="VALUE">
|
|
<block type="variables_get">
|
|
<field name="VAR">text</field>
|
|
</block>
|
|
</value>
|
|
<value name="FIND">
|
|
<shadow type="text">
|
|
<field name="TEXT">abc</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="text_charAt">
|
|
<value name="VALUE">
|
|
<block type="variables_get">
|
|
<field name="VAR">text</field>
|
|
</block>
|
|
</value>
|
|
</block>
|
|
<block type="text_getSubstring">
|
|
<value name="STRING">
|
|
<block type="variables_get">
|
|
<field name="VAR">text</field>
|
|
</block>
|
|
</value>
|
|
</block>
|
|
<block type="text_changeCase">
|
|
<value name="TEXT">
|
|
<shadow type="text">
|
|
<field name="TEXT">abc</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="text_trim">
|
|
<value name="TEXT">
|
|
<shadow type="text">
|
|
<field name="TEXT">abc</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="text_count">
|
|
<value name="SUB">
|
|
<shadow type="text"></shadow>
|
|
</value>
|
|
<value name="TEXT">
|
|
<shadow type="text"></shadow>
|
|
</value>
|
|
</block>
|
|
<block type="text_replace">
|
|
<value name="FROM">
|
|
<shadow type="text"></shadow>
|
|
</value>
|
|
<value name="TO">
|
|
<shadow type="text"></shadow>
|
|
</value>
|
|
<value name="TEXT">
|
|
<shadow type="text"></shadow>
|
|
</value>
|
|
</block>
|
|
<block type="text_reverse">
|
|
<value name="TEXT">
|
|
<shadow type="text"></shadow>
|
|
</value>
|
|
</block>
|
|
<label text="Input/Output:" web-class="ioLabel"></label>
|
|
<block type="text_print">
|
|
<value name="TEXT">
|
|
<shadow type="text">
|
|
<field name="TEXT">abc</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="text_prompt_ext">
|
|
<value name="TEXT">
|
|
<shadow type="text">
|
|
<field name="TEXT">abc</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
</category>
|
|
<category name="Lists" categorystyle="list_category">
|
|
<block type="lists_create_with">
|
|
<mutation items="0"></mutation>
|
|
</block>
|
|
<block type="lists_create_with"></block>
|
|
<block type="lists_repeat">
|
|
<value name="NUM">
|
|
<shadow type="math_number">
|
|
<field name="NUM">5</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="lists_length"></block>
|
|
<block type="lists_isEmpty"></block>
|
|
<block type="lists_indexOf">
|
|
<value name="VALUE">
|
|
<block type="variables_get">
|
|
<field name="VAR">list</field>
|
|
</block>
|
|
</value>
|
|
</block>
|
|
<block type="lists_getIndex">
|
|
<value name="VALUE">
|
|
<block type="variables_get">
|
|
<field name="VAR">list</field>
|
|
</block>
|
|
</value>
|
|
</block>
|
|
<block type="lists_setIndex">
|
|
<value name="LIST">
|
|
<block type="variables_get">
|
|
<field name="VAR">list</field>
|
|
</block>
|
|
</value>
|
|
</block>
|
|
<block type="lists_getSublist">
|
|
<value name="LIST">
|
|
<block type="variables_get">
|
|
<field name="VAR">list</field>
|
|
</block>
|
|
</value>
|
|
</block>
|
|
<block type="lists_split">
|
|
<value name="DELIM">
|
|
<shadow type="text">
|
|
<field name="TEXT">,</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="lists_sort"></block>
|
|
<block type="lists_reverse"></block>
|
|
</category>
|
|
<category name="Colour" categorystyle="colour_category">
|
|
<block type="colour_picker"></block>
|
|
<block type="colour_random"></block>
|
|
<block type="colour_rgb">
|
|
<value name="RED">
|
|
<shadow type="math_number">
|
|
<field name="NUM">100</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="GREEN">
|
|
<shadow type="math_number">
|
|
<field name="NUM">50</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="BLUE">
|
|
<shadow type="math_number">
|
|
<field name="NUM">0</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
<block type="colour_blend">
|
|
<value name="COLOUR1">
|
|
<shadow type="colour_picker">
|
|
<field name="COLOUR">#ff0000</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="COLOUR2">
|
|
<shadow type="colour_picker">
|
|
<field name="COLOUR">#3333ff</field>
|
|
</shadow>
|
|
</value>
|
|
<value name="RATIO">
|
|
<shadow type="math_number">
|
|
<field name="NUM">0.5</field>
|
|
</shadow>
|
|
</value>
|
|
</block>
|
|
</category>
|
|
<sep></sep>
|
|
<category name="Variables" categorystyle="variable_category" custom="VARIABLE_DYNAMIC"></category>
|
|
<category name="Functions" categorystyle="procedure_category" custom="PROCEDURE"></category>
|
|
</xml>
|
|
</body>
|
|
</html>
|