// Copyright (c) 2014-17 Walter Bender
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the The GNU Affero General Public
|
|
// License as published by the Free Software Foundation; either
|
|
// version 3 of the License, or (at your option) any later version.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA
|
|
|
|
// Minimum distance (squared) between two docks required before
|
|
// connecting them.
|
|
const MINIMUMDOCKDISTANCE = 400;
|
|
|
|
// Special value flags to uniquely identify these media blocks.
|
|
const CAMERAVALUE = '##__CAMERA__##';
|
|
const VIDEOVALUE = '##__VIDEO__##';
|
|
|
|
// Blocks holds the list of blocks and most of the block-associated
|
|
// methods, since most block manipulations are inter-block.
|
|
|
|
function Blocks () {
|
|
if (sugarizerCompatibility.isInsideSugarizer()) {
|
|
storage = sugarizerCompatibility.data;
|
|
} else {
|
|
storage = localStorage;
|
|
}
|
|
|
|
this.canvas = null;
|
|
this.stage = null;
|
|
this.refreshCanvas = null;
|
|
this.trashcan = null;
|
|
this.updateStage = null;
|
|
this.getStageScale = null;
|
|
|
|
// We keep a list of stacks in the trash.
|
|
this.trashStacks = [];
|
|
|
|
// We keep a dictionary for the proto blocks,
|
|
this.protoBlockDict = {}
|
|
// and a list of the blocks we create.
|
|
this.blockList = [];
|
|
|
|
// Track the time with mouse down.
|
|
this.mouseDownTime = 0;
|
|
this.longPressTimeout = null;
|
|
|
|
// "Copy stack" selects a stack for pasting. Are we selecting?
|
|
this.selectingStack = false;
|
|
// and what did we select?
|
|
this.selectedStack = null;
|
|
// and a copy of the selected stack for pasting.
|
|
this.selectedBlocksObj = null;
|
|
|
|
// If we somehow have a malformed block database (for example,
|
|
// from importing a corrupted datafile, we need to avoid infinite
|
|
// loops while crawling the block list.
|
|
this._loopCounter = 0;
|
|
this._sizeCounter = 0;
|
|
this._searchCounter = 0;
|
|
|
|
// We need a reference to the palettes.
|
|
this.palettes = null;
|
|
// Which block, if any, is highlighted?
|
|
this.highlightedBlock = null;
|
|
// Which block, if any, is active?
|
|
this.activeBlock = null;
|
|
// Are the blocks visible?
|
|
this.visible = true;
|
|
// The group of blocks being dragged or moved together
|
|
this.dragGroup = [];
|
|
// The blocks at the tops of stacks
|
|
this.stackList = [];
|
|
// The blocks that need expanding
|
|
this._expandablesList = [];
|
|
// Number of blocks to load
|
|
this._loadCounter = 0;
|
|
// Stacks of blocks that need adjusting as blocks are repositioned
|
|
// due to expanding and contracting or insertion into the flow.
|
|
this._adjustTheseDocks = [];
|
|
// Blocks that need collapsing after load.
|
|
this.blocksToCollapse = [];
|
|
// Arg blocks that need expanding after load.
|
|
this._checkTwoArgBlocks = [];
|
|
// Arg clamp blocks that need expanding after load.
|
|
this._checkArgClampBlocks = [];
|
|
// Clamp blocks that need expanding after load.
|
|
this._clampBlocksToCheck = [];
|
|
|
|
// We need to keep track of certain classes of blocks that exhibit
|
|
// different types of behavior.
|
|
|
|
// Blocks with parts that expand, e.g.,
|
|
this._expandableBlocks = [];
|
|
// Blocks that contain child flows of blocks
|
|
this.clampBlocks = [];
|
|
this.doubleExpandable = [];
|
|
this.argClampBlocks = [];
|
|
// Blocks that are used as arguments to other blocks
|
|
this.argBlocks = [];
|
|
// Blocks that return values
|
|
this.valueBlocks = [];
|
|
// Two-arg blocks with two arguments (expandable).
|
|
this.twoArgBlocks = [];
|
|
// Blocks that don't run when clicked.
|
|
this.noRunBlocks = [];
|
|
|
|
this._homeButtonContainers = [];
|
|
this.blockScale = DEFAULTBLOCKSCALE;
|
|
|
|
// We need to know if we are processing a copy or save stack command.
|
|
this.inLongPress = false;
|
|
|
|
// We stage deletion of prototype action blocks on the palette so
|
|
// as to avoid palette refresh race conditions.
|
|
this.deleteActionTimeout = 0;
|
|
|
|
this.setCanvas = function (canvas) {
|
|
this.canvas = canvas;
|
|
return this;
|
|
};
|
|
|
|
this.setStage = function (stage) {
|
|
this.stage = stage;
|
|
return this;
|
|
};
|
|
|
|
this.setRefreshCanvas = function (refreshCanvas) {
|
|
this.refreshCanvas = refreshCanvas;
|
|
return this;
|
|
};
|
|
|
|
this.setTrashcan = function (trashcan) {
|
|
this.trashcan = trashcan;
|
|
return this;
|
|
};
|
|
|
|
this.setUpdateStage = function (updateStage) {
|
|
this.updateStage = updateStage;
|
|
return this;
|
|
};
|
|
|
|
this.setGetStageScale = function (getStageScale) {
|
|
this.getStageScale = getStageScale;
|
|
return this;
|
|
};
|
|
|
|
// Change the scale of the blocks (and the protoblocks on the palette).
|
|
this.setBlockScale = function (scale) {
|
|
console.log('New block scale is ' + scale);
|
|
this.blockScale = scale;
|
|
|
|
// Regenerate all of the artwork at the new scale.
|
|
for (var blk = 0; blk < this.blockList.length; blk++) {
|
|
// if (!this.blockList[blk].trash) {
|
|
this.blockList[blk].resize(scale);
|
|
// }
|
|
}
|
|
|
|
this.findStacks();
|
|
for (var stack = 0; stack < this.stackList.length; stack++) {
|
|
// console.log('Adjust Docks: ' + this.blockList[this.stackList[stack]].name);
|
|
this.adjustDocks(this.stackList[stack], true);
|
|
}
|
|
|
|
// We reset the protoblock scale on the palettes, but don't
|
|
// modify the palettes themselves.
|
|
for (var palette in this.palettes.dict) {
|
|
for (var blk = 0; blk < this.palettes.dict[palette].protoList.length; blk++) {
|
|
this.palettes.dict[palette].protoList[blk].scale = scale;
|
|
}
|
|
}
|
|
};
|
|
|
|
// We need access to the msg block...
|
|
this.setMsgText = function (msgText) {
|
|
this.msgText = msgText;
|
|
};
|
|
|
|
// and the Error msg function.
|
|
this.setErrorMsg = function (errorMsg) {
|
|
this.errorMsg = errorMsg;
|
|
return this;
|
|
};
|
|
|
|
// We need access to the macro dictionary because we add to it.
|
|
this.setMacroDictionary = function (obj) {
|
|
this.macroDict = obj;
|
|
return this;
|
|
};
|
|
|
|
// We need access to the turtles list because we associate a
|
|
// turtle with each start block.
|
|
this.setTurtles = function (turtles) {
|
|
this.turtles = turtles;
|
|
return this;
|
|
};
|
|
|
|
// We need to access the "pseudo-Logo interpreter" when we click
|
|
// on blocks.
|
|
this.setLogo = function (logo) {
|
|
this.logo = logo;
|
|
return this;
|
|
};
|
|
|
|
// The scale of the graphics is determined by screen size.
|
|
this.setScale = function (scale) {
|
|
this.blockScale = scale;
|
|
return this;
|
|
};
|
|
|
|
// Toggle state of collapsible blocks.
|
|
this.toggleCollapsibles = function () {
|
|
for (var blk in this.blockList) {
|
|
var myBlock = this.blockList[blk];
|
|
if (COLLAPSABLES.indexOf(myBlock.name) !== -1 && !myBlock.trash) {
|
|
myBlock.collapseToggle();
|
|
}
|
|
}
|
|
};
|
|
|
|
// We need access to the go-home buttons and boundary.
|
|
this.setHomeContainers = function (containers, boundary) {
|
|
this._homeButtonContainers = containers;
|
|
this.boundary = boundary;
|
|
return this;
|
|
};
|
|
|
|
// set up copy/paste, dismiss, and copy-stack buttons
|
|
this.makeCopyPasteButtons = function (makeButton, updatePasteButton) {
|
|
var that = this;
|
|
this.updatePasteButton = updatePasteButton;
|
|
|
|
this.dismissButton = makeButton('cancel-button', '', 0, 0, 55, 0, this.stage);
|
|
this.dismissButton.visible = false;
|
|
|
|
this.saveStackButton = makeButton('save-blocks-button', _('Save stack'), 0, 0, 55, 0, this.stage);
|
|
this.saveStackButton.visible = false;
|
|
|
|
this.dismissButton.on('click', function (event) {
|
|
that.saveStackButton.visible = false;
|
|
that.dismissButton.visible = false;
|
|
that.inLongPress = false;
|
|
that.refreshCanvas();
|
|
});
|
|
|
|
this.saveStackButton.on('click', function (event) {
|
|
// Only invoked from action blocks.
|
|
var topBlock = that.findTopBlock(that.activeBlock);
|
|
that.inLongPress = false;
|
|
that.selectedStack = topBlock;
|
|
that.saveStackButton.visible = false;
|
|
that.dismissButton.visible = false;
|
|
that.saveStack();
|
|
that.refreshCanvas();
|
|
});
|
|
};
|
|
|
|
// Walk through all of the proto blocks in order to make lists of
|
|
// any blocks that need special treatment.
|
|
this.findBlockTypes = function () {
|
|
for (var proto in this.protoBlockDict) {
|
|
if (this.protoBlockDict[proto].expandable) {
|
|
this._expandableBlocks.push(this.protoBlockDict[proto].name);
|
|
}
|
|
if (this.protoBlockDict[proto].style === 'clamp') {
|
|
this.clampBlocks.push(this.protoBlockDict[proto].name);
|
|
}
|
|
if (this.protoBlockDict[proto].style === 'argclamp') {
|
|
this.argClampBlocks.push(this.protoBlockDict[proto].name);
|
|
}
|
|
if (this.protoBlockDict[proto].style === 'argflowclamp') {
|
|
this.clampBlocks.push(this.protoBlockDict[proto].name);
|
|
}
|
|
if (this.protoBlockDict[proto].style === 'argclamparg') {
|
|
this.argClampBlocks.push(this.protoBlockDict[proto].name);
|
|
this.argBlocks.push(this.protoBlockDict[proto].name);
|
|
}
|
|
if (this.protoBlockDict[proto].style === 'twoarg') {
|
|
this.twoArgBlocks.push(this.protoBlockDict[proto].name);
|
|
}
|
|
if (this.protoBlockDict[proto].style === 'arg') {
|
|
this.argBlocks.push(this.protoBlockDict[proto].name);
|
|
}
|
|
if (this.protoBlockDict[proto].style === 'value') {
|
|
this.argBlocks.push(this.protoBlockDict[proto].name);
|
|
this.valueBlocks.push(this.protoBlockDict[proto].name);
|
|
}
|
|
if (this.protoBlockDict[proto].style === 'doubleclamp') {
|
|
this.doubleExpandable.push(this.protoBlockDict[proto].name);
|
|
}
|
|
}
|
|
};
|
|
|
|
this._actionBlock = function (name) {
|
|
return ['do', 'doArg', 'calc', 'calcArg'].indexOf(name) !== -1;
|
|
};
|
|
|
|
this._namedActionBlock = function (name) {
|
|
return ['nameddo', 'nameddoArg', 'namedcalc', 'namedcalcArg'].indexOf(name) !== -1;
|
|
};
|
|
|
|
// Adjust the docking postions of all blocks in the current drag
|
|
// group.
|
|
this._adjustBlockPositions = function () {
|
|
if (this.dragGroup.length < 2) {
|
|
return;
|
|
}
|
|
|
|
// console.log('Adjust Docks: ' + this.blockList[this.dragGroup[0]].name);
|
|
this.adjustDocks(this.dragGroup[0], true)
|
|
};
|
|
|
|
// Adjust the size of the clamp in an expandable block when blocks
|
|
// are inserted into (or removed from) the child flow. This is a
|
|
// common operation for start and action blocks, but also for
|
|
// repeat, forever, if, etc.
|
|
this._adjustExpandableClampBlock = function () {
|
|
if (this._clampBlocksToCheck.length === 0) {
|
|
return;
|
|
}
|
|
|
|
var obj = this._clampBlocksToCheck.pop();
|
|
var blk = obj[0];
|
|
var clamp = obj[1];
|
|
|
|
var myBlock = this.blockList[blk];
|
|
|
|
if (myBlock.isArgFlowClampBlock()) {
|
|
// Make sure myBlock is a clamp block.
|
|
} else if (myBlock.isArgBlock() || myBlock.isTwoArgBlock()) {
|
|
return;
|
|
} else if (myBlock.isArgClamp()) {
|
|
// We handle ArgClamp blocks elsewhere.
|
|
this._adjustArgClampBlock([blk]);
|
|
return;
|
|
}
|
|
|
|
function clampAdjuster(blocks, blk, myBlock, clamp) {
|
|
// First we need to count up the number of (and size of) the
|
|
// blocks inside the clamp; The child flow is usually the
|
|
// second-to-last argument.
|
|
|
|
if (myBlock.isArgFlowClampBlock()) {
|
|
var c = 1; // 0: outie; and 1: child flow
|
|
} else if (clamp === 0) {
|
|
var c = myBlock.connections.length - 2;
|
|
} else { // e.g., Bottom clamp in if-then-else
|
|
var c = myBlock.connections.length - 3;
|
|
}
|
|
|
|
blocks._sizeCounter = 0;
|
|
var childFlowSize = 1;
|
|
if (c > 0 && myBlock.connections[c] != null) {
|
|
childFlowSize = Math.max(blocks._getStackSize(myBlock.connections[c]), 1);
|
|
}
|
|
|
|
// Adjust the clamp size to match the size of the child
|
|
// flow.
|
|
var plusMinus = childFlowSize - myBlock.clampCount[clamp];
|
|
if (plusMinus !== 0) {
|
|
if (!(childFlowSize === 0 && myBlock.clampCount[clamp] === 1)) {
|
|
myBlock.updateSlots(clamp, plusMinus);
|
|
}
|
|
}
|
|
|
|
// Recurse through the list.
|
|
setTimeout(function () {
|
|
if (blocks._clampBlocksToCheck.length > 0) {
|
|
blocks._adjustExpandableClampBlock();
|
|
}
|
|
}, 250);
|
|
};
|
|
|
|
clampAdjuster(this, blk, myBlock, clamp);
|
|
};
|
|
|
|
// Returns the block size.
|
|
this._getBlockSize = function (blk) {
|
|
var myBlock = this.blockList[blk];
|
|
return myBlock.size;
|
|
};
|
|
|
|
// Adjust the slot sizes of arg clamps.
|
|
this._adjustArgClampBlock = function (argBlocksToCheck) {
|
|
if (argBlocksToCheck.length === 0) {
|
|
return;
|
|
}
|
|
|
|
var blk = argBlocksToCheck.pop();
|
|
var myBlock = this.blockList[blk];
|
|
|
|
// Which connection do we start with?
|
|
if (['doArg', 'calcArg'].indexOf(myBlock.name) !== -1) {
|
|
var ci = 2;
|
|
} else {
|
|
var ci = 1;
|
|
}
|
|
|
|
// Get the current slot list.
|
|
var slotList = myBlock.argClampSlots;
|
|
|
|
var update = false;
|
|
// Determine the size of each argument.
|
|
for (var i = 0; i < slotList.length; i++) {
|
|
var c = myBlock.connections[ci + i];
|
|
var size = 1; // Minimum size
|
|
if (c != null) {
|
|
size = Math.max(this._getBlockSize(c), 1);
|
|
}
|
|
if (slotList[i] !== size) {
|
|
slotList[i] = size;
|
|
update = true;
|
|
}
|
|
}
|
|
if (update) {
|
|
myBlock.updateArgSlots(slotList);
|
|
}
|
|
};
|
|
|
|
// We also adjust the size of twoarg blocks. It is similar to how
|
|
// we adjust clamps, but enough different that it is in its own
|
|
// function.
|
|
this._adjustExpandableTwoArgBlock = function (argBlocksToCheck) {
|
|
if (argBlocksToCheck.length === 0) {
|
|
return;
|
|
}
|
|
|
|
var blk = argBlocksToCheck.pop();
|
|
var myBlock = this.blockList[blk];
|
|
|
|
// Determine the size of the first argument.
|
|
var c = myBlock.connections[1];
|
|
var firstArgumentSize = 1; // Minimum size
|
|
if (c != null) {
|
|
firstArgumentSize = Math.max(this._getBlockSize(c), 1);
|
|
}
|
|
|
|
// Expand/contract block by plusMinus.
|
|
var plusMinus = firstArgumentSize - myBlock.clampCount[0];
|
|
if (plusMinus !== 0) {
|
|
if (!(firstArgumentSize === 0)) {
|
|
myBlock.updateSlots(0, plusMinus);
|
|
}
|
|
}
|
|
};
|
|
|
|
this._addRemoveVspaceBlock = function (blk) {
|
|
var myBlock = this.blockList[blk];
|
|
|
|
var c = myBlock.connections[myBlock.connections.length - 2];
|
|
var secondArgumentSize = 1;
|
|
if (c != null) {
|
|
var secondArgumentSize = Math.max(this._getBlockSize(c), 1);
|
|
}
|
|
|
|
var that = this;
|
|
|
|
var vSpaceCount = howManyVSpaceBlocksBelow(blk);
|
|
if (secondArgumentSize < vSpaceCount + 1) {
|
|
// Remove a vspace block
|
|
var n = Math.abs(secondArgumentSize - vSpaceCount - 1);
|
|
for (var i = 0; i < n; i++) {
|
|
var lastConnection = myBlock.connections.length - 1;
|
|
var vspaceBlock = this.blockList[myBlock.connections[lastConnection]];
|
|
var nextBlockIndex = vspaceBlock.connections[1];
|
|
myBlock.connections[lastConnection] = nextBlockIndex;
|
|
if (nextBlockIndex != null) {
|
|
this.blockList[nextBlockIndex].connections[0] = blk;
|
|
}
|
|
vspaceBlock.connections = [null, null];
|
|
vspaceBlock.trash = true;
|
|
vspaceBlock.hide();
|
|
}
|
|
} else if (secondArgumentSize > vSpaceCount + 1) {
|
|
// Add vspace blocks
|
|
var n = secondArgumentSize - vSpaceCount - 1;
|
|
var nextBlock = last(myBlock.connections);
|
|
var thisBlock = myBlock;
|
|
var newPos = this.blockList.length;
|
|
|
|
var that = this;
|
|
|
|
function vspaceAdjuster(args) { // nextBlock, vspace, i, n
|
|
var thisBlock = args[0];
|
|
var nextBlock = args[1];
|
|
var vspace = args[2];
|
|
var i = args[3];
|
|
var n = args[4];
|
|
var vspaceBlock = that.blockList[vspace];
|
|
var lastDock = last(thisBlock.docks);
|
|
var dx = lastDock[0] - vspaceBlock.docks[0][0];
|
|
var dy = lastDock[1] - vspaceBlock.docks[0][1];
|
|
vspaceBlock.container.x = thisBlock.container.x + dx;
|
|
vspaceBlock.container.y = thisBlock.container.y + dy;
|
|
vspaceBlock.connections[0] = that.blockList.indexOf(thisBlock);
|
|
vspaceBlock.connections[1] = nextBlock;
|
|
thisBlock.connections[thisBlock.connections.length - 1] = vspace;
|
|
if (nextBlock) {
|
|
that.blockList[nextBlock].connections[0] = vspace;
|
|
}
|
|
if (i + 1 < n) {
|
|
var newPos = that.blockList.length;
|
|
thisBlock = last(that.blockList);
|
|
nextBlock = last(thisBlock.connections);
|
|
that._makeNewBlockWithConnections('vspace', newPos, [null, null], vspaceAdjuster, [thisBlock, nextBlock, newPos, i + 1, n]);
|
|
}
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('vspace', newPos, [null, null], vspaceAdjuster, [thisBlock, nextBlock, newPos, 0, n]);
|
|
};
|
|
|
|
function howManyVSpaceBlocksBelow(blk) {
|
|
// Need to know how many vspace blocks are below the block
|
|
// we're checking against.
|
|
var nextBlock = last(that.blockList[blk].connections);
|
|
if (nextBlock && that.blockList[nextBlock].name === 'vspace') {
|
|
return 1 + howManyVSpaceBlocksBelow(nextBlock);
|
|
// Recurse until it isn't a vspace
|
|
}
|
|
return 0;
|
|
};
|
|
};
|
|
|
|
this._getStackSize = function (blk) {
|
|
// How many block units in this stack?
|
|
var size = 0;
|
|
this._sizeCounter += 1;
|
|
if (this._sizeCounter > this.blockList.length * 2) {
|
|
console.log('Infinite loop encountered detecting size of expandable block? ' + blk);
|
|
return size;
|
|
}
|
|
|
|
if (blk == null) {
|
|
return size;
|
|
}
|
|
|
|
var myBlock = this.blockList[blk];
|
|
if (myBlock == null) {
|
|
console.log('Something very broken in _getStackSize.');
|
|
}
|
|
|
|
if (myBlock.isClampBlock()) {
|
|
var c = myBlock.connections.length - 2;
|
|
var csize = 0;
|
|
if (c > 0) {
|
|
var cblk = myBlock.connections[c];
|
|
if (cblk != null) {
|
|
csize = this._getStackSize(cblk);
|
|
}
|
|
if (csize === 0) {
|
|
size = 1; // minimum of 1 slot in clamp
|
|
} else {
|
|
size = csize;
|
|
}
|
|
}
|
|
|
|
if (myBlock.isDoubleClampBlock()) {
|
|
var c = myBlock.connections.length - 3;
|
|
var csize = 0;
|
|
if (c > 0) {
|
|
var cblk = myBlock.connections[c];
|
|
if (cblk != null) {
|
|
csize = this._getStackSize(cblk);
|
|
}
|
|
|
|
if (csize === 0) {
|
|
size += 1; // minimum of 1 slot in clamp
|
|
} else {
|
|
size += csize;
|
|
}
|
|
}
|
|
}
|
|
|
|
// add top and bottom of clamp
|
|
size += myBlock.size;
|
|
} else {
|
|
size = myBlock.size;
|
|
}
|
|
|
|
// check on any connected block
|
|
if (!myBlock.isValueBlock()) {
|
|
var cblk = last(myBlock.connections);
|
|
if (cblk != null) {
|
|
size += this._getStackSize(cblk);
|
|
}
|
|
}
|
|
return size;
|
|
};
|
|
|
|
this.adjustDocks = function (blk, resetLoopCounter) {
|
|
// Give a block, adjust the dock positions
|
|
// of all of the blocks connected to it
|
|
|
|
var myBlock = this.blockList[blk];
|
|
|
|
// For when we come in from makeBlock
|
|
if (resetLoopCounter != null) {
|
|
this._loopCounter = 0;
|
|
}
|
|
|
|
// These checks are to test for malformed data. All blocks
|
|
// should have connections.
|
|
if (myBlock == null) {
|
|
console.log('Saw a null block: ' + blk);
|
|
return;
|
|
}
|
|
|
|
if (myBlock.connections == null) {
|
|
console.log('Saw a block with null connections: ' + blk);
|
|
return;
|
|
}
|
|
|
|
if (myBlock.connections.length === 0) {
|
|
console.log('Saw a block with [] connections: ' + blk);
|
|
return;
|
|
}
|
|
|
|
// Value blocks only have one dock.
|
|
if (myBlock.docks.length === 1) {
|
|
return;
|
|
}
|
|
|
|
this._loopCounter += 1;
|
|
if (this._loopCounter > this.blockList.length * 2) {
|
|
console.log('Infinite loop encountered while adjusting docks: ' + blk + ' ' + this.blockList);
|
|
return;
|
|
}
|
|
|
|
// Walk through each connection except the parent block; the
|
|
// exception being the parent block of boolean 2arg blocks,
|
|
// since the dock[0] position can change.
|
|
if (myBlock.isTwoArgBooleanBlock()) {
|
|
var start = 0;
|
|
} else {
|
|
var start = 1;
|
|
}
|
|
for (var c = start; c < myBlock.connections.length; c++) {
|
|
// Get the dock position for this connection.
|
|
var bdock = myBlock.docks[c];
|
|
|
|
// Find the connecting block.
|
|
var cblk = myBlock.connections[c];
|
|
// Nothing connected here so continue to the next connection.
|
|
if (cblk == null) {
|
|
continue;
|
|
}
|
|
|
|
// Another database integrety check.
|
|
if (this.blockList[cblk] == null) {
|
|
console.log('This is not good: we encountered a null block: ' + cblk);
|
|
continue;
|
|
}
|
|
|
|
// Find the dock position in the connected block.
|
|
var foundMatch = false;
|
|
for (var b = 0; b < this.blockList[cblk].connections.length; b++) {
|
|
if (this.blockList[cblk].connections[b] === blk) {
|
|
foundMatch = true;
|
|
break
|
|
}
|
|
}
|
|
|
|
// Yet another database integrety check.
|
|
if (!foundMatch) {
|
|
console.log('Did not find match for ' + myBlock.name + ' (' + blk + ') and ' + this.blockList[cblk].name + ' (' + cblk + ')');
|
|
console.log(myBlock.connections);
|
|
console.log(this.blockList[cblk].connections);
|
|
break;
|
|
}
|
|
|
|
var cdock = this.blockList[cblk].docks[b];
|
|
|
|
if (c > 0) {
|
|
// Move the connected block...
|
|
var dx = bdock[0] - cdock[0];
|
|
var dy = bdock[1] - cdock[1];
|
|
if (myBlock.container == null) {
|
|
console.log('Does this ever happen any more?')
|
|
} else {
|
|
var nx = myBlock.container.x + dx;
|
|
var ny = myBlock.container.y + dy;
|
|
}
|
|
this._moveBlock(cblk, nx, ny);
|
|
} else {
|
|
// or it's parent.
|
|
var dx = cdock[0] - bdock[0];
|
|
var dy = cdock[1] - bdock[1];
|
|
var nx = this.blockList[cblk].container.x + dx;
|
|
var ny = this.blockList[cblk].container.y + dy;
|
|
this._moveBlock(blk, nx, ny);
|
|
}
|
|
|
|
if (c > 0) {
|
|
// Recurse on connected blocks.
|
|
this.adjustDocks(cblk, true);
|
|
}
|
|
}
|
|
};
|
|
|
|
this.addDefaultBlock = function (parentblk, oldBlock, skipOldBlock) {
|
|
// Add an action name whenever the user removes the name from
|
|
// an action block. Add a box name whenever the user removes
|
|
// the name from a storein block. Add a Silence block
|
|
// whenever the user removes all the blocks from a Note block.
|
|
if (parentblk == null) {
|
|
return;
|
|
}
|
|
|
|
if (this.blockList[parentblk].name === 'action') {
|
|
var cblk = this.blockList[parentblk].connections[1];
|
|
if (cblk == null) {
|
|
var that = this;
|
|
postProcess = function (args) {
|
|
var parentblk = args[0];
|
|
var oldBlock = args[1];
|
|
|
|
var blk = that.blockList.length - 1;
|
|
that.blockList[parentblk].connections[1] = blk;
|
|
that.blockList[blk].value = that.findUniqueActionName(_('action'));
|
|
var label = that.blockList[blk].value;
|
|
if (label.length > 8) {
|
|
label = label.substr(0, 7) + '...';
|
|
}
|
|
that.blockList[blk].text.text = label;
|
|
that.blockList[blk].container.updateCache();
|
|
|
|
if (that.blockList[blk].value !== that.blockList[oldBlock].value) {
|
|
|
|
that.newNameddoBlock(that.blockList[blk].value, that.actionHasReturn(parentblk), that.actionHasArgs(parentblk));
|
|
var blockPalette = that.palettes.dict['action'];
|
|
for (var b = 0; b < blockPalette.protoList.length; b++) {
|
|
var protoblock = blockPalette.protoList[b];
|
|
if (protoblock.name === 'nameddo' && protoblock.defaults[0] === that.blockList[oldBlock].value) {
|
|
setTimeout(function () {
|
|
blockPalette.remove(protoblock, that.blockList[oldBlock].value);
|
|
delete that.protoBlockDict['myDo_' + that.blockList[oldBlock].value];
|
|
that.palettes.hide();
|
|
that.palettes.updatePalettes('action');
|
|
that.palettes.show();
|
|
}, 500);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
that.renameNameddos(that.blockList[oldBlock].value, that.blockList[blk].value);
|
|
if (skipOldBlock) {
|
|
that.renameDos(that.blockList[oldBlock].value, that.blockList[blk].value, oldBlock);
|
|
} else {
|
|
that.renameDos(that.blockList[oldBlock].value, that.blockList[blk].value);
|
|
}
|
|
}
|
|
|
|
that.adjustDocks(parentblk, true);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('text', 0, [parentblk], postProcess, [parentblk, oldBlock], false);
|
|
}
|
|
} else if (this.blockList[parentblk].name === 'storein') {
|
|
var cblk = this.blockList[parentblk].connections[1];
|
|
if (cblk == null) {
|
|
var that = this;
|
|
postProcess = function (args) {
|
|
var parentblk = args[0];
|
|
var oldBlock = args[1];
|
|
|
|
var blk = that.blockList.length - 1;
|
|
that.blockList[parentblk].connections[1] = blk;
|
|
that.blockList[blk].value = _('box');
|
|
var label = that.blockList[blk].value;
|
|
if (label.length > 8) {
|
|
label = label.substr(0, 7) + '...';
|
|
}
|
|
that.blockList[blk].text.text = label;
|
|
that.blockList[blk].container.updateCache();
|
|
|
|
that.adjustDocks(parentblk, true);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('text', 0, [parentblk], postProcess, [parentblk, oldBlock], false);
|
|
}
|
|
} else if (['newnote', 'osctime'].indexOf(this.blockList[parentblk].name) !== -1) {
|
|
var cblk = this.blockList[parentblk].connections[2];
|
|
if (cblk == null) {
|
|
var blkname = 'vspace';
|
|
var newVspaceBlock = this.makeBlock(blkname, '__NOARG__');
|
|
this.blockList[parentblk].connections[2] = newVspaceBlock;
|
|
this.blockList[newVspaceBlock].connections[0] = parentblk;
|
|
var blkname = 'rest2';
|
|
var newSilenceBlock = this.makeBlock(blkname, '__NOARG__');
|
|
this.blockList[newSilenceBlock].connections[0] = newVspaceBlock;
|
|
this.blockList[newSilenceBlock].connections[1] = null;
|
|
this.blockList[newVspaceBlock].connections[1] = newSilenceBlock;
|
|
} else if (this.blockList[cblk].name === 'vspace' && this.blockList[cblk].connections[1] == null) {
|
|
var blkname = 'rest2';
|
|
var newSilenceBlock = this.makeBlock(blkname, '__NOARG__');
|
|
this.blockList[newSilenceBlock].connections[0] = cblk;
|
|
this.blockList[newSilenceBlock].connections[1] = null;
|
|
this.blockList[cblk].connections[1] = newSilenceBlock;
|
|
}
|
|
}
|
|
};
|
|
|
|
this.deleteNextDefault = function (thisBlock) {
|
|
// Remove the Silence block from a Note block if another block
|
|
// is inserted above the silence block.
|
|
var thisBlockobj = this.blockList[thisBlock];
|
|
for (var i = 1; i < thisBlockobj.connections.length; i++) {
|
|
if (thisBlockobj.connections[i] && this.blockList[thisBlockobj.connections[i]].name === 'rest2') {
|
|
var silenceBlock = thisBlockobj.connections[i];
|
|
var silenceBlockobj = this.blockList[silenceBlock];
|
|
silenceBlockobj.hide();
|
|
silenceBlockobj.trash = true;
|
|
this.blockList[thisBlock].connections[i] = silenceBlockobj.connections[1];
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
this.deletePreviousDefault = function (thisBlock) {
|
|
// Remove the Silence block from a Note block if another block
|
|
// is inserted just after the Silence block.
|
|
var thisBlockobj = this.blockList[thisBlock];
|
|
if (thisBlockobj && this.blockList[thisBlockobj.connections[0]] && this.blockList[thisBlockobj.connections[0]].name === 'rest2') {
|
|
var silenceBlock = thisBlockobj.connections[0];
|
|
var silenceBlockobj = this.blockList[silenceBlock];
|
|
silenceBlockobj.hide();
|
|
silenceBlockobj.trash = true;
|
|
|
|
for (var i = 0; i < this.blockList[silenceBlockobj.connections[0]].connections.length; i++) {
|
|
if (this.blockList[silenceBlockobj.connections[0]].connections[i] === silenceBlock) {
|
|
this.blockList[silenceBlockobj.connections[0]].connections[i] = thisBlock;
|
|
break;
|
|
}
|
|
}
|
|
|
|
thisBlockobj.connections[0] = silenceBlockobj.connections[0];
|
|
}
|
|
|
|
return thisBlockobj.connections[0];
|
|
};
|
|
|
|
this.blockMoved = function (thisBlock) {
|
|
// When a block is moved, we have lots of things to check:
|
|
// (0) Is it inside of a expandable block?
|
|
// Is it an arg inside an arg clamp?
|
|
// (1) Is it an arg block connected to a two-arg block?
|
|
// (2) Disconnect its connection[0];
|
|
// (3) Look for a new connection;
|
|
// Is it potentially an arg inside an arg clamp?
|
|
// (4) Is it an arg block connected to a 2-arg block?
|
|
// (5) Is it a pitch block being inserted or removed from
|
|
// a Note clamp? In which case, we may have to remove
|
|
// or add a silence block.
|
|
// (6) Is it the name of an action block? In which case we
|
|
// need to check to see if we need to rename it.
|
|
// (7) Is it the name of a storein block? In which case we
|
|
// need to check to see if we need to add a palette entry.
|
|
// (8) And we need to recheck if it inside of a expandable block.
|
|
|
|
// Find any containing expandable blocks.
|
|
this._clampBlocksToCheck = [];
|
|
if (thisBlock == null) {
|
|
console.log('blockMoved called with null block.');
|
|
return;
|
|
}
|
|
|
|
var blk = this._insideExpandableBlock(thisBlock);
|
|
var expandableLoopCounter = 0;
|
|
|
|
var parentblk = null;
|
|
if (blk != null) {
|
|
parentblk = blk;
|
|
}
|
|
|
|
var actionCheck = false;
|
|
|
|
while (blk != null) {
|
|
expandableLoopCounter += 1;
|
|
if (expandableLoopCounter > 2 * this.blockList.length) {
|
|
console.log('Infinite loop encountered checking for expandables?');
|
|
break;
|
|
}
|
|
|
|
this._clampBlocksToCheck.push([blk, 0]);
|
|
blk = this._insideExpandableBlock(blk);
|
|
}
|
|
|
|
this._checkTwoArgBlocks = [];
|
|
var checkArgBlocks = [];
|
|
var myBlock = this.blockList[thisBlock];
|
|
if (myBlock == null) {
|
|
console.log('null block found in blockMoved method: ' + thisBlock);
|
|
return;
|
|
}
|
|
|
|
var c = myBlock.connections[0];
|
|
if (c != null) {
|
|
var cBlock = this.blockList[c];
|
|
}
|
|
|
|
// If it is an arg block, where is it coming from?
|
|
if (myBlock.isArgBlock() && c != null) {
|
|
// We care about twoarg (2arg) blocks with
|
|
// connections to the first arg;
|
|
if (this.blockList[c].isTwoArgBlock() || this.blockList[c].isArgClamp()) {
|
|
if (cBlock.connections[1] === thisBlock) {
|
|
this._checkTwoArgBlocks.push(c);
|
|
}
|
|
} else if (this.blockList[c].isArgBlock() && this.blockList[c].isExpandableBlock() || this.blockList[c].isArgClamp()) {
|
|
if (cBlock.connections[1] === thisBlock) {
|
|
this._checkTwoArgBlocks.push(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Disconnect from connection[0] (both sides of the connection).
|
|
if (c != null) {
|
|
// Disconnect both ends of the connection.
|
|
for (var i = 1; i < cBlock.connections.length; i++) {
|
|
if (cBlock.connections[i] === thisBlock) {
|
|
cBlock.connections[i] = null;
|
|
break;
|
|
}
|
|
}
|
|
myBlock.connections[0] = null;
|
|
this.raiseStackToTop(thisBlock);
|
|
}
|
|
|
|
// Look for a new connection.
|
|
var x1 = myBlock.container.x + myBlock.docks[0][0];
|
|
var y1 = myBlock.container.y + myBlock.docks[0][1];
|
|
|
|
// Find the nearest dock; if it is close enough, make the
|
|
// connection.
|
|
var newBlock = null;
|
|
var newConnection = null;
|
|
var min = (MINIMUMDOCKDISTANCE/DEFAULTBLOCKSCALE) * this.blockScale;
|
|
var blkType = myBlock.docks[0][2];
|
|
|
|
// Is the added block above the silence block or below?
|
|
var insertAfterDefault = true;
|
|
|
|
for (var b = 0; b < this.blockList.length; b++) {
|
|
// Don't connect to yourself.
|
|
if (b === thisBlock) {
|
|
continue;
|
|
}
|
|
|
|
// Don't connect to a collapsed block.
|
|
if (this.blockList[b].collapsed) {
|
|
continue;
|
|
}
|
|
|
|
// Don't connect to a block in the trash.
|
|
if (this.blockList[b].trash) {
|
|
continue;
|
|
}
|
|
|
|
for (var i = 1; i < this.blockList[b].connections.length; i++) {
|
|
// When converting from Python projects to JS format,
|
|
// sometimes extra null connections are added. We need
|
|
// to ignore them.
|
|
if (i === this.blockList[b].docks.length) {
|
|
break;
|
|
}
|
|
|
|
if ((i === this.blockList[b].connections.length - 1) && (this.blockList[b].connections[i] != null) && (this.blockList[this.blockList[b].connections[i]].isNoHitBlock())) {
|
|
// Don't break the connection between a block and
|
|
// a hidden block below it.
|
|
continue;
|
|
} else if ((['backward', 'status'].indexOf(this.blockList[b].name) !== -1) && (i === 1) && (this.blockList[b].connections[1] != null) && (this.blockList[this.blockList[b].connections[1]].isNoHitBlock())) {
|
|
// Don't break the connection betweem a backward
|
|
// block and a hidden block attached to its clamp.
|
|
continue;
|
|
} else if (this.blockList[b].name === 'action' && (i === 2) && (this.blockList[b].connections[2] != null) && (this.blockList[this.blockList[b].connections[2]].isNoHitBlock())) {
|
|
// Don't break the connection betweem an action
|
|
// block and a hidden block attached to its clamp.
|
|
continue;
|
|
}
|
|
|
|
// Look for available connections.
|
|
if (this._testConnectionType(blkType, this.blockList[b].docks[i][2])) {
|
|
var x2 = this.blockList[b].container.x + this.blockList[b].docks[i][0];
|
|
var y2 = this.blockList[b].container.y + this.blockList[b].docks[i][1];
|
|
var dist = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
|
|
if (dist < min) {
|
|
newBlock = b;
|
|
newConnection = i;
|
|
min = dist;
|
|
}
|
|
} else {
|
|
// TODO: bounce away from illegal connection?
|
|
// only if the distance was small
|
|
// console.log('cannot not connect these two block types');
|
|
}
|
|
}
|
|
}
|
|
|
|
if (newBlock != null) {
|
|
// We found a match.
|
|
myBlock.connections[0] = newBlock;
|
|
var connection = this.blockList[newBlock].connections[newConnection];
|
|
if (connection == null) {
|
|
if (this.blockList[newBlock].isArgClamp()) {
|
|
// If it is an arg clamp, we may have to adjust
|
|
// the slot size.
|
|
if ((this.blockList[newBlock].name === 'doArg' || this.blockList[newBlock].name === 'calcArg') && newConnection === 1) {
|
|
} else if (['doArg', 'nameddoArg'].indexOf(this.blockList[newBlock].name) !== -1 && newConnection === this.blockList[newBlock].connections.length - 1) {
|
|
} else {
|
|
// Get the size of the block we are inserting
|
|
// adding.
|
|
var size = this._getBlockSize(thisBlock);
|
|
// console.log('inserting block of size ' + size + ' to arg clamp ' + this.blockList[newBlock].name);
|
|
// Get the current slot list.
|
|
var slotList = this.blockList[newBlock].argClampSlots;
|
|
|
|
// Which slot is this block in?
|
|
if (['doArg', 'calcArg'].indexOf(this.blockList[newBlock].name) !== -1) {
|
|
var si = newConnection - 2;
|
|
} else {
|
|
var si = newConnection - 1;
|
|
}
|
|
if (slotList[si] !== size) {
|
|
slotList[si] = size;
|
|
this.blockList[newBlock].updateArgSlots(slotList);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Three scenarios in which we may be overriding an
|
|
// existing connection:
|
|
// (1) if it is an argClamp, add a new slot below the
|
|
// current block;
|
|
// (2) if it is an arg block, replace it; or
|
|
// (3) if it is a flow block, insert it into the flow.
|
|
// A few corner cases: Whenever we connect (or disconnect)
|
|
// from an action block (c[1] arg), we need to ensure we have
|
|
// a unique action name; Whenever we connect to a newnote
|
|
// block (c[2] flow), we need to ensure we have either a silence
|
|
// block or a pitch block. And if we are connecting to a
|
|
// storein block, we need to ensure that there is a palette
|
|
// entry for the new namedbox.
|
|
insertAfterDefault = false;
|
|
if (this.blockList[newBlock].isArgClamp()) {
|
|
if ((this.blockList[newBlock].name === 'doArg' || this.blockList[newBlock].name === 'calcArg') && newConnection === 1) {
|
|
// If it is the action name then treat it like
|
|
// a standard replacement.
|
|
this.blockList[connection].connections[0] = null;
|
|
this.findDragGroup(connection);
|
|
for (var c = 0; c < this.dragGroup.length; c++) {
|
|
this.moveBlockRelative(this.dragGroup[c], 40, 40);
|
|
}
|
|
} else if (['doArg', 'nameddoArg'].indexOf(this.blockList[newBlock].name) !== -1 && newConnection === this.blockList[newBlock].connections.length - 1) {
|
|
// If it is the bottom of the flow, insert as
|
|
// usual.
|
|
var bottom = this.findBottomBlock(thisBlock);
|
|
this.blockList[connection].connections[0] = bottom;
|
|
this.blockList[bottom].connections[this.blockList[bottom].connections.length - 1] = connection;
|
|
} else {
|
|
// Move the block in the current slot down one
|
|
// slot (cascading and creating a new slot if
|
|
// necessary).
|
|
|
|
// Get the size of the block we are inserting adding.
|
|
var size = this._getBlockSize(thisBlock);
|
|
|
|
// Get the current slot list.
|
|
var slotList = this.blockList[newBlock].argClampSlots;
|
|
// Which slot is this block in?
|
|
var ci = this.blockList[newBlock].connections.indexOf(connection);
|
|
if (['doArg', 'calcArg'].indexOf(this.blockList[newBlock].name) !== -1) {
|
|
var si = ci - 2;
|
|
} else {
|
|
var si = ci - 1;
|
|
}
|
|
|
|
var emptySlot = null;
|
|
var emptyConnection = null;
|
|
// Is there an empty slot below?
|
|
for (var emptySlot = si; emptySlot < slotList.length; emptySlot++) {
|
|
if (this.blockList[newBlock].connections[ci + emptySlot - si] == null) {
|
|
emptyConnection = ci + emptySlot - si;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (emptyConnection == null) {
|
|
slotList.push(1);
|
|
this._newLocalArgBlock(slotList.length);
|
|
emptyConnection = ci + emptySlot - si;
|
|
this.blockList[newBlock].connections.push(null);
|
|
|
|
// Slide everything down one slot.
|
|
for (var i = slotList.length - 1; i > si + 1; i--) {
|
|
slotList[i] = slotList[i - 1];
|
|
}
|
|
for (var i = this.blockList[newBlock].connections.length - 1; i > ci + 1; i--) {
|
|
this.blockList[newBlock].connections[i] = this.blockList[newBlock].connections[i - 1];
|
|
}
|
|
}
|
|
// The new block is added below the current
|
|
// connection...
|
|
newConnection += 1;
|
|
// Set its slot size too.
|
|
slotList[si + 1] = size;
|
|
|
|
this.blockList[newBlock].updateArgSlots(slotList);
|
|
}
|
|
} else if (myBlock.isArgBlock()) {
|
|
this.blockList[connection].connections[0] = null;
|
|
this.findDragGroup(connection);
|
|
for (var c = 0; c < this.dragGroup.length; c++) {
|
|
this.moveBlockRelative(this.dragGroup[c], 40, 40);
|
|
}
|
|
|
|
// We need to rename the action stack.
|
|
if (this.blockList[newBlock].name === 'action') {
|
|
actionCheck = true;
|
|
|
|
if (myBlock.value !== this.blockList[connection].value) {
|
|
// Temporarily disconnect to ensure we don't
|
|
// find myBlock when looking for a unique name.
|
|
var c = myBlock.connections[0];
|
|
myBlock.connections[0] = null;
|
|
var name = this.findUniqueActionName(myBlock.value);
|
|
myBlock.connections[0] = c;
|
|
|
|
if (name !== myBlock.value) {
|
|
myBlock.value = name;
|
|
var label = name;
|
|
if (label.length > 8) {
|
|
label = label.substr(0, 7) + '...';
|
|
}
|
|
myBlock.text.text = label;
|
|
myBlock.container.updateCache();
|
|
}
|
|
|
|
var that = this;
|
|
setTimeout(function () {
|
|
// A previously disconnected name may have left
|
|
// an entry in the palette we need to remove.
|
|
var name = that.blockList[connection].value;
|
|
if (that.protoBlockDict['myDo_' + name] != undefined) {
|
|
delete that.protoBlockDict['myDo_' + name];
|
|
that.palettes.dict['action'].hideMenu(true);
|
|
}
|
|
|
|
that.newNameddoBlock(myBlock.value, that.actionHasReturn(newBlock), that.actionHasArgs(newBlock));
|
|
var blockPalette = that.palettes.dict['action'];
|
|
for (var b = 0; b < blockPalette.protoList.length; b++) {
|
|
var protoblock = blockPalette.protoList[b];
|
|
if (protoblock.name === 'nameddo' && protoblock.staticLabels[0] === that.blockList[connection].value) {
|
|
setTimeout(function () {
|
|
blockPalette.remove(protoblock, that.blockList[connection].value);
|
|
delete that.protoBlockDict['myDo_' + that.blockList[connection].value];
|
|
that.palettes.hide();
|
|
that.palettes.updatePalettes('action');
|
|
that.palettes.show();
|
|
}, 500);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
that.renameNameddos(that.blockList[connection].value, myBlock.value);
|
|
that.renameDos(that.blockList[connection].value, myBlock.value);
|
|
}, 750);
|
|
}
|
|
} else if (this.blockList[newBlock].name === 'storein') {
|
|
// We may need to add new storein and namedo
|
|
// blocks to the palette.
|
|
if (myBlock.value !== 'box') {
|
|
this.newStoreinBlock(myBlock.value);
|
|
this.newNamedboxBlock(myBlock.value);
|
|
var that = this;
|
|
setTimeout(function () {
|
|
that.palettes.hide();
|
|
that.palettes.updatePalettes('boxes');
|
|
that.palettes.show();
|
|
}, 500);
|
|
}
|
|
}
|
|
} else if (!this.blockList[thisBlock].isArgFlowClampBlock()) {
|
|
var bottom = this.findBottomBlock(thisBlock);
|
|
this.blockList[connection].connections[0] = bottom;
|
|
this.blockList[bottom].connections[this.blockList[bottom].connections.length - 1] = connection;
|
|
}
|
|
}
|
|
|
|
this.blockList[newBlock].connections[newConnection] = thisBlock;
|
|
|
|
// Remove the silence block (if it is present) after
|
|
// adding a new block inside of a note block.
|
|
if (this._insideNoteBlock(thisBlock) != null) {
|
|
// If blocks are inserted above the silence block.
|
|
if (insertAfterDefault) {
|
|
newBlock = this.deletePreviousDefault(thisBlock);
|
|
} else {
|
|
this.deleteNextDefault(bottom);
|
|
}
|
|
}
|
|
|
|
// If we attached a name to an action block, check to see
|
|
// if we need to rename it.
|
|
if (this.blockList[newBlock].name === 'action' && !actionCheck) {
|
|
// Is there already another action block with this name?
|
|
for (var b = 0; b < this.blockList.length; b++) {
|
|
if (b === newBlock) continue;
|
|
if (this.blockList[b].name === 'action') {
|
|
if (this.blockList[b].connections[1] != null) {
|
|
if (this.blockList[this.blockList[b].connections[1]].value === this.blockList[thisBlock].value) {
|
|
this.blockList[thisBlock].value = this.findUniqueActionName(this.blockList[thisBlock].value);
|
|
var label = this.blockList[thisBlock].value;
|
|
if (label.length > 8) {
|
|
label = label.substr(0, 7) + '...';
|
|
}
|
|
this.blockList[thisBlock].text.text = label;
|
|
this.blockList[thisBlock].container.updateCache();
|
|
this.newNameddoBlock(this.blockList[thisBlock].value, this.actionHasReturn(b), this.actionHasArgs(b));
|
|
this.setActionProtoVisiblity(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// console.log('Adjust Docks: ' + this.blockList[newBlock].name);
|
|
this.adjustDocks(newBlock, true);
|
|
// TODO: some graphical feedback re new connection?
|
|
}
|
|
|
|
// If it is an arg block, where is it coming from?
|
|
// FIXME: improve mechanism for testing block types.
|
|
if ((myBlock.isArgBlock() || myBlock.name === 'calcArg' || myBlock.name === 'namedcalcArg') && newBlock != null) {
|
|
// We care about twoarg blocks with connections to the
|
|
// first arg;
|
|
if (this.blockList[newBlock].isTwoArgBlock()) {
|
|
if (this.blockList[newBlock].connections[1] === thisBlock) {
|
|
if (this._checkTwoArgBlocks.indexOf(newBlock) === -1) {
|
|
this._checkTwoArgBlocks.push(newBlock);
|
|
}
|
|
}
|
|
} else if (this.blockList[newBlock].isArgBlock() && this.blockList[newBlock].isExpandableBlock()) {
|
|
if (this.blockList[newBlock].connections[1] === thisBlock) {
|
|
if (this._checkTwoArgBlocks.indexOf(newBlock) === -1) {
|
|
this._checkTwoArgBlocks.push(newBlock);
|
|
}
|
|
}
|
|
}
|
|
// We also care about the second-to-last connection to an
|
|
// arg block.
|
|
var n = this.blockList[newBlock].connections.length;
|
|
if (this.blockList[newBlock].connections[n - 2] === thisBlock) {
|
|
// Only flow blocks, but not ArgClamps
|
|
if (!this.blockList[newBlock].isArgClamp() && this.blockList[newBlock].docks[n - 1][2] === 'in') {
|
|
checkArgBlocks.push(newBlock);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.addDefaultBlock(parentblk, thisBlock, actionCheck);
|
|
|
|
// Put block adjustments inside a slight delay to make the
|
|
// addition/substraction of vspace and changes of block shape
|
|
// appear less abrupt (and it can be a little racy).
|
|
var that = this;
|
|
setTimeout(function () {
|
|
// If we changed the contents of a arg block, we may need a vspace.
|
|
if (checkArgBlocks.length > 0) {
|
|
for (var i = 0; i < checkArgBlocks.length; i++) {
|
|
that._addRemoveVspaceBlock(checkArgBlocks[i]);
|
|
}
|
|
}
|
|
|
|
// If we changed the contents of a two-arg block, we need to
|
|
// adjust it.
|
|
if (that._checkTwoArgBlocks.length > 0) {
|
|
that._adjustExpandableTwoArgBlock(that._checkTwoArgBlocks);
|
|
}
|
|
|
|
// First, adjust the docks for any blocks that may have
|
|
// had a vspace added.
|
|
for (var i = 0; i < checkArgBlocks.length; i++) {
|
|
// console.log('Adjust Docks: ' + this.blockList[checkArgBlocks[i]].name);
|
|
that.adjustDocks(checkArgBlocks[i], true);
|
|
}
|
|
|
|
// Next, recheck if the connection is inside of a
|
|
// expandable block.
|
|
var blk = that._insideExpandableBlock(thisBlock);
|
|
var expandableLoopCounter = 0;
|
|
while (blk != null) {
|
|
// Extra check for malformed data.
|
|
expandableLoopCounter += 1;
|
|
if (expandableLoopCounter > 2 * that.blockList.length) {
|
|
console.log('Infinite loop checking for expandables?');
|
|
console.log(that.blockList);
|
|
break;
|
|
}
|
|
|
|
if (that.blockList[blk].name === 'ifthenelse') {
|
|
that._clampBlocksToCheck.push([blk, 0]);
|
|
that._clampBlocksToCheck.push([blk, 1]);
|
|
} else {
|
|
that._clampBlocksToCheck.push([blk, 0]);
|
|
}
|
|
|
|
blk = that._insideExpandableBlock(blk);
|
|
}
|
|
|
|
that._adjustExpandableClampBlock();
|
|
that.refreshCanvas();
|
|
}, 250);
|
|
};
|
|
|
|
this._testConnectionType = function (type1, type2) {
|
|
// Can these two blocks dock?
|
|
if (type1 === 'in' && type2 === 'out') {
|
|
return true;
|
|
}
|
|
if (type1 === 'out' && type2 === 'in') {
|
|
return true;
|
|
}
|
|
if (type1 === 'numberin' && ['numberout', 'anyout'].indexOf(type2) !== -1) {
|
|
return true;
|
|
}
|
|
if (['numberout', 'anyout'].indexOf(type1) !== -1 && type2 === 'numberin') {
|
|
return true;
|
|
}
|
|
if (type1 === 'textin' && ['textout', 'anyout'].indexOf(type2) !== -1) {
|
|
return true;
|
|
}
|
|
if (['textout', 'anyout'].indexOf(type1) !== -1 && type2 === 'textin') {
|
|
return true;
|
|
}
|
|
if (type1 === 'booleanout' && type2 === 'booleanin') {
|
|
return true;
|
|
}
|
|
if (type1 === 'booleanin' && type2 === 'booleanout') {
|
|
return true;
|
|
}
|
|
if (type1 === 'mediain' && type2 === 'mediaout') {
|
|
return true;
|
|
}
|
|
if (type1 === 'mediaout' && type2 === 'mediain') {
|
|
return true;
|
|
}
|
|
if (type1 === 'mediain' && type2 === 'textout') {
|
|
return true;
|
|
}
|
|
if (type2 === 'mediain' && type1 === 'textout') {
|
|
return true;
|
|
}
|
|
if (type1 === 'filein' && type2 === 'fileout') {
|
|
return true;
|
|
}
|
|
if (type1 === 'fileout' && type2 === 'filein') {
|
|
return true;
|
|
}
|
|
if (type1 === 'solfegein' && ['anyout', 'solfegeout', 'textout', 'noteout', 'numberout'].indexOf(type2) !== -1) {
|
|
return true;
|
|
}
|
|
if (type2 === 'solfegein' && ['anyout', 'solfegeout', 'textout', 'noteout', 'numberout'].indexOf(type1) !== -1) {
|
|
return true;
|
|
}
|
|
if (type1 === 'notein' && ['solfegeout', 'textout', 'noteout'].indexOf(type2) !== -1) {
|
|
return true;
|
|
}
|
|
if (type2 === 'notein' && ['solfegeout', 'textout', 'noteout'].indexOf(type1) !== -1) {
|
|
return true;
|
|
}
|
|
if (type1 === 'anyin' && ['textout', 'mediaout', 'numberout', 'anyout', 'fileout', 'solfegeout', 'noteout'].indexOf(type2) !== -1) {
|
|
return true;
|
|
}
|
|
if (type2 === 'anyin' && ['textout', 'mediaout', 'numberout', 'anyout', 'fileout', 'solfegeout', 'noteout'].indexOf(type1) !== -1) {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
this.updateBlockPositions = function () {
|
|
// Create the block image if it doesn't yet exist.
|
|
for (var blk = 0; blk < this.blockList.length; blk++) {
|
|
this._moveBlock(blk, this.blockList[blk].container.x, this.blockList[blk].container.y);
|
|
}
|
|
};
|
|
|
|
this.bringToTop = function () {
|
|
// Move all the blocks to the top layer of the stage
|
|
this._adjustTheseStacks = [];
|
|
|
|
for (var blk in this.blockList) {
|
|
var myBlock = this.blockList[blk];
|
|
/*
|
|
this.stage.removeChild(myBlock.container);
|
|
this.stage.addChild(myBlock.container);
|
|
if (myBlock.collapseContainer != null) {
|
|
this.stage.removeChild(myBlock.collapseContainer);
|
|
this.stage.addChild(myBlock.collapseContainer);
|
|
}
|
|
*/
|
|
if (myBlock.connections[0] == null) {
|
|
this._adjustTheseStacks.push(blk);
|
|
}
|
|
}
|
|
for (var blk = 0; blk < this._adjustTheseStacks.length; blk++) {
|
|
// console.log('Adjust Stack: ' + this.blockList[this._adjustTheseStacks[blk]].name);
|
|
this.raiseStackToTop(this._adjustTheseStacks[blk]);
|
|
}
|
|
|
|
this.refreshCanvas();
|
|
};
|
|
|
|
this.checkBounds = function () {
|
|
var onScreen = true;
|
|
for (var blk = 0; blk < this.blockList.length; blk++) {
|
|
if (this.blockList[blk].connections[0] == null) {
|
|
if (this.blockList[blk].offScreen(this.boundary)) {
|
|
this._homeButtonContainers[0].visible = true;
|
|
this._homeButtonContainers[1].visible = false;
|
|
this.boundary.show();
|
|
onScreen = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (onScreen) {
|
|
this._homeButtonContainers[0].visible = false;
|
|
this._homeButtonContainers[1].visible = true;
|
|
this.boundary.hide();
|
|
}
|
|
};
|
|
|
|
this._moveBlock = function (blk, x, y) {
|
|
// Move a block (and its label) to x, y.
|
|
var myBlock = this.blockList[blk];
|
|
if (myBlock.container != null) {
|
|
myBlock.container.x = x;
|
|
myBlock.container.y = y;
|
|
|
|
if (myBlock.collapseContainer != null) {
|
|
myBlock.collapseContainer.x = x + COLLAPSEBUTTONXOFF * (this.blockList[blk].protoblock.scale / 2);
|
|
myBlock.collapseContainer.y = y + COLLAPSEBUTTONYOFF * (this.blockList[blk].protoblock.scale / 2);
|
|
}
|
|
|
|
this.checkBounds();
|
|
} else {
|
|
console.log('No container yet for block ' + myBlock.name);
|
|
}
|
|
};
|
|
|
|
this.moveBlockRelative = function (blk, dx, dy) {
|
|
// Move a block (and its label) by dx, dy.
|
|
if (this.inLongPress) {
|
|
this.saveStackButton.visible = false;
|
|
this.dismissButton.visible = false;
|
|
this.inLongPress = false;
|
|
}
|
|
|
|
var myBlock = this.blockList[blk];
|
|
if (myBlock.container != null) {
|
|
myBlock.container.x += dx;
|
|
myBlock.container.y += dy;
|
|
|
|
if (myBlock.collapseContainer != null) {
|
|
myBlock.collapseContainer.x += dx;
|
|
myBlock.collapseContainer.y += dy;
|
|
}
|
|
|
|
this.checkBounds();
|
|
} else {
|
|
console.log('No container yet for block ' + myBlock.name);
|
|
}
|
|
};
|
|
|
|
this.moveStackRelative = function (blk, dx, dy) {
|
|
this.findDragGroup(blk)
|
|
if (this.dragGroup.length > 0) {
|
|
for (var b = 0; b < this.dragGroup.length; b++) {
|
|
this.moveBlockRelative(this.dragGroup[b], dx, dy);
|
|
}
|
|
}
|
|
};
|
|
|
|
this.updateBlockText = function (blk) {
|
|
// When we create new blocks, we may not have assigned the
|
|
// value yet.
|
|
var myBlock = this.blockList[blk];
|
|
var maxLength = 8;
|
|
if (myBlock.text == null) {
|
|
return;
|
|
}
|
|
|
|
if (myBlock.name === 'loadFile') {
|
|
try {
|
|
var label = myBlock.value[0].toString();
|
|
} catch (e) {
|
|
var label = _('open file');
|
|
}
|
|
maxLength = 10;
|
|
} else if (myBlock.name === 'solfege') {
|
|
var obj = splitSolfege(myBlock.value);
|
|
var label = i18nSolfege(obj[0]);
|
|
var attr = obj[1];
|
|
|
|
if (attr !== '♮') {
|
|
label += attr;
|
|
}
|
|
} else if (myBlock.name === 'eastindiansolfege') {
|
|
var obj = splitSolfege(myBlock.value);
|
|
var label = WESTERN2EISOLFEGENAMES[obj[0]];
|
|
var attr = obj[1];
|
|
|
|
if (attr !== '♮') {
|
|
label += attr;
|
|
}
|
|
} else {
|
|
var label = myBlock.value.toString();
|
|
}
|
|
|
|
if (label.length > maxLength) {
|
|
label = label.substr(0, maxLength - 1) + '...';
|
|
}
|
|
|
|
myBlock.text.text = label;
|
|
|
|
// Make sure text is on top.
|
|
var z = myBlock.container.getNumChildren() - 1;
|
|
myBlock.container.setChildIndex(myBlock.text, z);
|
|
|
|
if (myBlock.loadComplete) {
|
|
myBlock.container.updateCache();
|
|
} else {
|
|
console.log('Load not yet complete for (' + blk + ') ' + myBlock.name);
|
|
}
|
|
};
|
|
|
|
this.findTopBlock = function (blk) {
|
|
// Find the top block in a stack.
|
|
if (blk == null) {
|
|
return null;
|
|
}
|
|
|
|
var myBlock = this.blockList[blk];
|
|
if (myBlock.connections == null) {
|
|
return blk;
|
|
}
|
|
|
|
if (myBlock.connections.length === 0) {
|
|
return blk;
|
|
}
|
|
|
|
// Test for corrupted-connection scenario.
|
|
if (myBlock.connections.length > 1 && myBlock.connections[0] != null && myBlock.connections[0] === last(myBlock.connections)) {
|
|
console.log('WARNING: CORRUPTED BLOCK DATA. Block ' + myBlock.name + ' (' + blk + ') is connected to the same block ' + this.blockList[myBlock.connections[0]].name + ' (' + myBlock.connections[0] + ') twice.');
|
|
return blk;
|
|
}
|
|
|
|
var topBlockLoop = 0;
|
|
while (myBlock.connections[0] != null) {
|
|
topBlockLoop += 1;
|
|
if (topBlockLoop > 2 * this.blockList.length) {
|
|
// Could happen if the block data is malformed.
|
|
console.log('infinite loop finding topBlock?');
|
|
console.log(this.blockList.indexOf(myBlock) + ' ' + myBlock.name);
|
|
break;
|
|
}
|
|
blk = myBlock.connections[0];
|
|
myBlock = this.blockList[blk];
|
|
}
|
|
return blk;
|
|
};
|
|
|
|
this.sameGeneration = function (firstBlk, childBlk) {
|
|
if (firstBlk == null || childBlk == null) {
|
|
return false;
|
|
}
|
|
|
|
if (firstBlk === childBlk) {
|
|
return true;
|
|
}
|
|
|
|
var myBlock = this.blockList[firstBlk];
|
|
if (myBlock.connections == null) {
|
|
return false;
|
|
}
|
|
|
|
if (myBlock.connections.length === 0) {
|
|
return false;
|
|
}
|
|
|
|
var bottomBlockLoop = 0;
|
|
while (last(myBlock.connections) != null) {
|
|
bottomBlockLoop += 1;
|
|
if (bottomBlockLoop > 2 * this.blockList.length) {
|
|
// Could happen if the block data is malformed.
|
|
console.log('infinite loop finding bottomBlock?');
|
|
break;
|
|
}
|
|
blk = last(myBlock.connections);
|
|
myBlock = this.blockList[blk];
|
|
if (blk === childBlk) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
this.findBottomBlock = function (blk) {
|
|
// Find the bottom block in a stack.
|
|
if (blk == null) {
|
|
return null;
|
|
}
|
|
|
|
var myBlock = this.blockList[blk];
|
|
if (myBlock.connections == null) {
|
|
return blk;
|
|
}
|
|
|
|
if (myBlock.connections.length === 0) {
|
|
return blk;
|
|
}
|
|
|
|
var bottomBlockLoop = 0;
|
|
while (last(myBlock.connections) != null) {
|
|
bottomBlockLoop += 1;
|
|
if (bottomBlockLoop > 2 * this.blockList.length) {
|
|
// Could happen if the block data is malformed.
|
|
console.log('infinite loop finding bottomBlock?');
|
|
break;
|
|
}
|
|
blk = last(myBlock.connections);
|
|
myBlock = this.blockList[blk];
|
|
}
|
|
return blk;
|
|
};
|
|
|
|
this.findStacks = function () {
|
|
// Find any blocks with null in the first connection.
|
|
this.stackList = [];
|
|
for (var i = 0; i < this.blockList.length; i++) {
|
|
if (this.blockList[i].connections[0] == null) {
|
|
this.stackList.push(i)
|
|
}
|
|
}
|
|
};
|
|
|
|
this._findClamps = function () {
|
|
// Find any clamp blocks.
|
|
this._expandablesList = [];
|
|
this.findStacks(); // We start by finding the stacks
|
|
for (var i = 0; i < this.stackList.length; i++) {
|
|
this._searchCounter = 0;
|
|
this._searchForExpandables(this.stackList[i]);
|
|
}
|
|
|
|
this._searchForArgFlow();
|
|
};
|
|
|
|
this._findTwoArgs = function () {
|
|
// Find any expandable arg blocks.
|
|
this._expandablesList = [];
|
|
for (var i = 0; i < this.blockList.length; i++) {
|
|
if (this.blockList[i].isArgBlock() && this.blockList[i].isExpandableBlock()) {
|
|
this._expandablesList.push(i);
|
|
} else if (this.blockList[i].isTwoArgBlock()) {
|
|
this._expandablesList.push(i);
|
|
}
|
|
}
|
|
};
|
|
|
|
this._searchForArgFlow = function () {
|
|
for (var blk = 0; blk < this.blockList.length; blk++) {
|
|
if (this.blockList[blk].isArgFlowClampBlock()) {
|
|
this._expandablesList.push(blk);
|
|
}
|
|
}
|
|
};
|
|
|
|
this._searchForExpandables = function (blk) {
|
|
// Find the expandable blocks below blk in a stack.
|
|
while (blk != null && this.blockList[blk] != null && !this.blockList[blk].isValueBlock()) {
|
|
// More checks for malformed or corrupted block data.
|
|
this._searchCounter += 1;
|
|
if (this._searchCounter > 2 * this.blockList.length) {
|
|
console.log('infinite loop searching for Expandables? ' + this._searchCounter);
|
|
console.log(blk + ' ' + this.blockList[blk].name);
|
|
break;
|
|
}
|
|
|
|
if (this.blockList[blk].isClampBlock()) {
|
|
this._expandablesList.push(blk);
|
|
var c = this.blockList[blk].connections.length - 2;
|
|
this._searchForExpandables(this.blockList[blk].connections[c]);
|
|
if (this.blockList[blk].name === 'ifthenelse') {
|
|
// search top clamp too
|
|
var c = 2;
|
|
this._searchForExpandables(this.blockList[blk].connections[c]);
|
|
}
|
|
} else if (this.blockList[blk].isArgClamp()) {
|
|
this._expandablesList.push(blk);
|
|
}
|
|
|
|
if (this.blockList[blk].connections.length > 1) {
|
|
blk = last(this.blockList[blk].connections);
|
|
} else {
|
|
// A value block only connects back to its parent, so
|
|
// end the search here.
|
|
blk = null;
|
|
}
|
|
}
|
|
};
|
|
|
|
this._expandTwoArgs = function () {
|
|
// Expand expandable 2-arg blocks as needed.
|
|
this._findTwoArgs();
|
|
this._adjustExpandableTwoArgBlock(this._expandablesList);
|
|
this.refreshCanvas();
|
|
};
|
|
|
|
this._expandClamps = function () {
|
|
// Expand expandable clamp blocks as needed.
|
|
this._findClamps();
|
|
this._clampBlocksToCheck = [];
|
|
for (var i = 0; i < this._expandablesList.length; i++) {
|
|
if (this.blockList[this._expandablesList[i]].name === 'ifthenelse') {
|
|
this._clampBlocksToCheck.push([this._expandablesList[i], 0]);
|
|
this._clampBlocksToCheck.push([this._expandablesList[i], 1]);
|
|
} else {
|
|
this._clampBlocksToCheck.push([this._expandablesList[i], 0]);
|
|
}
|
|
}
|
|
|
|
this._adjustExpandableClampBlock();
|
|
this.refreshCanvas();
|
|
};
|
|
|
|
this.changeDisabledStatus = function (name, flag) {
|
|
// Some blocks, e.g., sensor blocks for Butia, change their
|
|
// appearance depending upon if they have been enabled or
|
|
// disabled.
|
|
for (var blk in this.blockList) {
|
|
var myBlock = this.blockList[blk];
|
|
if (myBlock.name === name) {
|
|
myBlock.protoblock.disabled = flag;
|
|
myBlock.regenerateArtwork(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
this.unhighlightAll = function () {
|
|
for (var blk in this.blockList) {
|
|
this.unhighlight(blk);
|
|
}
|
|
};
|
|
|
|
this.unhighlight = function (blk) {
|
|
if (!this.visible) {
|
|
return;
|
|
}
|
|
if (blk != null) {
|
|
var thisBlock = blk;
|
|
} else {
|
|
var thisBlock = this.highlightedBlock;
|
|
}
|
|
if (thisBlock != null) {
|
|
|
|
this.blockList[thisBlock].unhighlight();
|
|
}
|
|
if (this.highlightedBlock = thisBlock) {
|
|
this.highlightedBlock = null;
|
|
}
|
|
};
|
|
|
|
this.highlight = function (blk, unhighlight) {
|
|
if (!this.visible) {
|
|
return;
|
|
}
|
|
if (blk != null) {
|
|
if (unhighlight) {
|
|
this.unhighlight(null);
|
|
}
|
|
this.blockList[blk].highlight();
|
|
this.highlightedBlock = blk;
|
|
}
|
|
};
|
|
|
|
this.hide = function () {
|
|
for (var blk in this.blockList) {
|
|
this.blockList[blk].hide();
|
|
}
|
|
this.visible = false;
|
|
};
|
|
|
|
this.show = function () {
|
|
for (var blk in this.blockList) {
|
|
this.blockList[blk].show();
|
|
}
|
|
this.visible = true;
|
|
};
|
|
|
|
this._makeNewBlockWithConnections = function (name, blockOffset, connections, postProcess, postProcessArg, collapsed) {
|
|
if (typeof(collapsed) === 'undefined') {
|
|
collapsed = false
|
|
}
|
|
myBlock = this.makeNewBlock(name, postProcess, postProcessArg);
|
|
if (myBlock == null) {
|
|
console.log('could not make block ' + name);
|
|
return;
|
|
}
|
|
|
|
// myBlock.collapsed = !collapsed;
|
|
for (var c = 0; c < connections.length; c++) {
|
|
if (c === myBlock.docks.length) {
|
|
break;
|
|
}
|
|
if (connections[c] == null) {
|
|
myBlock.connections.push(null);
|
|
} else {
|
|
myBlock.connections.push(connections[c] + blockOffset);
|
|
}
|
|
}
|
|
};
|
|
|
|
this.makeNewBlock = function (name, postProcess, postProcessArg) {
|
|
// Create a new block
|
|
if (!name in this.protoBlockDict) {
|
|
// Should never happen: nop blocks should be substituted
|
|
console.log('makeNewBlock: no prototype for ' + name);
|
|
return null;
|
|
}
|
|
|
|
if (this.protoBlockDict[name] == null) {
|
|
// Should never happen
|
|
console.log('makeNewBlock: no prototype for ' + name);
|
|
return null;
|
|
}
|
|
|
|
// Deprecated
|
|
// If we drag in a synth block, we need to load the synth.
|
|
if (['sine', 'sawtooth', 'triangle', 'square'].indexOf(name) !== -1) {
|
|
if (_THIS_IS_MUSIC_BLOCKS_) {
|
|
this.logo.synth.loadSynth(name);
|
|
}
|
|
}
|
|
|
|
if (['namedbox', 'nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg'].indexOf(name) !== -1) {
|
|
this.blockList.push(new Block(this.protoBlockDict[name], this, postProcessArg[1]));
|
|
} else if (name === 'namedarg') {
|
|
this.blockList.push(new Block(this.protoBlockDict[name], this, 'arg ' + postProcessArg[1]));
|
|
} else {
|
|
this.blockList.push(new Block(this.protoBlockDict[name], this));
|
|
}
|
|
|
|
if (last(this.blockList) == null) {
|
|
// Should never happen
|
|
console.log('failed to make protoblock for ' + name);
|
|
return null;
|
|
}
|
|
|
|
// We copy the dock because expandable blocks modify it.
|
|
var myBlock = last(this.blockList);
|
|
myBlock.copySize();
|
|
|
|
// We may need to do some postProcessing to the block
|
|
myBlock.postProcess = postProcess;
|
|
myBlock.postProcessArg = postProcessArg;
|
|
|
|
// We need a container for the block graphics.
|
|
myBlock.container = new createjs.Container();
|
|
this.stage.addChild(myBlock.container);
|
|
myBlock.container.snapToPixelEnabled = true;
|
|
myBlock.container.x = 0;
|
|
myBlock.container.y = 0;
|
|
|
|
// and we need to load the images into the container.
|
|
myBlock.imageLoad();
|
|
return myBlock;
|
|
};
|
|
|
|
this.makeBlock = function (name, arg) {
|
|
// Make a new block from a proto block.
|
|
// Called from palettes.
|
|
|
|
if (name === 'text') {
|
|
console.log('makeBlock ' + name + ' ' + arg);
|
|
}
|
|
var postProcess = null;
|
|
var postProcessArg = null;
|
|
var that = this;
|
|
var thisBlock = this.blockList.length;
|
|
if (name === 'start') {
|
|
postProcess = function (thisBlock) {
|
|
that.blockList[thisBlock].value = that.turtles.turtleList.length;
|
|
that.turtles.addTurtle(that.blockList[thisBlock]);
|
|
};
|
|
|
|
postProcessArg = thisBlock;
|
|
} else if (name === 'drum') {
|
|
postProcess = function (thisBlock) {
|
|
that.blockList[thisBlock].value = that.turtles.turtleList.length;
|
|
that.turtles.addDrum(that.blockList[thisBlock]);
|
|
};
|
|
|
|
postProcessArg = thisBlock;
|
|
} else if (name === 'text') {
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
that.blockList[thisBlock].text.text = value;
|
|
that.blockList[thisBlock].container.updateCache();
|
|
};
|
|
|
|
postProcessArg = [thisBlock, _('text')];
|
|
} else if (name === 'solfege') {
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
that.blockList[thisBlock].text.text = value;
|
|
that.blockList[thisBlock].container.updateCache();
|
|
};
|
|
|
|
postProcessArg = [thisBlock, 'sol'];
|
|
} else if (name === 'eastindiansolfege') {
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
that.blockList[thisBlock].text.text = WESTERN2EISOLFEGENAMES[value];
|
|
that.blockList[thisBlock].container.updateCache();
|
|
};
|
|
|
|
postProcessArg = [thisBlock, 'sol'];
|
|
} else if (name === 'notename') {
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
that.blockList[thisBlock].text.text = value;
|
|
that.blockList[thisBlock].container.updateCache();
|
|
};
|
|
|
|
postProcessArg = [thisBlock, 'G'];
|
|
} else if (name === 'drumname') {
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
that.blockList[thisBlock].text.text = value;
|
|
that.blockList[thisBlock].container.updateCache();
|
|
};
|
|
|
|
postProcessArg = [thisBlock, 'kick'];
|
|
} else if (name === 'voicename') {
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
that.blockList[thisBlock].text.text = value;
|
|
that.blockList[thisBlock].container.updateCache();
|
|
};
|
|
|
|
postProcessArg = [thisBlock, 'sine'];
|
|
} else if (name === 'modename') {
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
that.blockList[thisBlock].text.text = value;
|
|
that.blockList[thisBlock].container.updateCache();
|
|
};
|
|
|
|
postProcessArg = [thisBlock, 'Major'];
|
|
} else if (name === 'number') {
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = Number(args[1]);
|
|
that.blockList[thisBlock].value = value;
|
|
that.blockList[thisBlock].text.text = value.toString();
|
|
that.blockList[thisBlock].container.updateCache();
|
|
};
|
|
|
|
postProcessArg = [thisBlock, NUMBERBLOCKDEFAULT];
|
|
} else if (name === 'media') {
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
if (value == null) {
|
|
that.blockList[thisBlock].image = 'images/load-media.svg';
|
|
} else {
|
|
that.blockList[thisBlock].image = null;
|
|
}
|
|
};
|
|
|
|
postProcessArg = [thisBlock, null];
|
|
} else if (name === 'camera') {
|
|
postProcess = function (args) {
|
|
console.log('post process camera ' + args[1]);
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = CAMERAVALUE;
|
|
if (value == null) {
|
|
that.blockList[thisBlock].image = 'images/camera.svg';
|
|
} else {
|
|
that.blockList[thisBlock].image = null;
|
|
}
|
|
};
|
|
|
|
postProcessArg = [thisBlock, null];
|
|
} else if (name === 'video') {
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = VIDEOVALUE;
|
|
if (value == null) {
|
|
that.blockList[thisBlock].image = 'images/video.svg';
|
|
} else {
|
|
that.blockList[thisBlock].image = null;
|
|
}
|
|
};
|
|
|
|
postProcessArg = [thisBlock, null];
|
|
} else if (name === 'loadFile') {
|
|
postProcess = function (args) {
|
|
that.updateBlockText(args[0]);
|
|
};
|
|
|
|
postProcessArg = [thisBlock, null];
|
|
} else if (['namedbox', 'nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg', 'namedarg'].indexOf(name) !== -1) {
|
|
postProcess = function (args) {
|
|
that.blockList[thisBlock].value = null;
|
|
that.blockList[thisBlock].privateData = args[1];
|
|
};
|
|
|
|
postProcessArg = [thisBlock, arg];
|
|
}
|
|
|
|
var protoFound = false;
|
|
for (var proto in that.protoBlockDict) {
|
|
if (that.protoBlockDict[proto].name === name) {
|
|
if (arg === '__NOARG__') {
|
|
that.makeNewBlock(proto, postProcess, postProcessArg);
|
|
protoFound = true;
|
|
break;
|
|
} else if (that.protoBlockDict[proto].defaults[0] === arg) {
|
|
that.makeNewBlock(proto, postProcess, postProcessArg);
|
|
protoFound = true;
|
|
break;
|
|
} else if (['namedbox', 'nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg', 'namedarg'].indexOf(name) !== -1) {
|
|
if (that.protoBlockDict[proto].defaults[0] === undefined) {
|
|
that.makeNewBlock(proto, postProcess, postProcessArg);
|
|
protoFound = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!protoFound) {
|
|
console.log(name + ' not found!!');
|
|
}
|
|
|
|
var blk = this.blockList.length - 1;
|
|
var myBlock = this.blockList[blk];
|
|
for (var i = 0; i < myBlock.docks.length; i++) {
|
|
myBlock.connections.push(null);
|
|
}
|
|
|
|
// Attach default args if any
|
|
var cblk = blk + 1;
|
|
for (var i = 0; i < myBlock.protoblock.defaults.length; i++) {
|
|
var value = myBlock.protoblock.defaults[i];
|
|
|
|
if (myBlock.name === 'action') {
|
|
// Make sure we don't make two actions with the same name.
|
|
value = this.findUniqueActionName(_('action'));
|
|
if (value !== _('action')) {
|
|
// TODO: are there return or arg blocks?
|
|
this.newNameddoBlock(value, false, false);
|
|
this.palettes.hide();
|
|
this.palettes.updatePalettes('action');
|
|
this.palettes.show();
|
|
}
|
|
}
|
|
|
|
var that = this;
|
|
var thisBlock = this.blockList.length;
|
|
if (myBlock.docks.length > i && myBlock.docks[i + 1][2] === 'anyin') {
|
|
if (value == null) {
|
|
console.log('cannot set default value');
|
|
} else if (typeof(value) === 'string') {
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
var label = value.toString();
|
|
if (label.length > 8) {
|
|
label = label.substr(0, 7) + '...';
|
|
}
|
|
that.blockList[thisBlock].text.text = label;
|
|
that.blockList[thisBlock].container.updateCache();
|
|
};
|
|
|
|
this.makeNewBlock('text', postProcess, [thisBlock, value]);
|
|
} else {
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = Number(args[1]);
|
|
that.blockList[thisBlock].value = value;
|
|
that.blockList[thisBlock].text.text = value.toString();
|
|
};
|
|
|
|
this.makeNewBlock('number', postProcess, [thisBlock, value]);
|
|
}
|
|
} else if (myBlock.docks[i + 1][2] === 'textin') {
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
var label = value.toString();
|
|
if (label.length > 8) {
|
|
label = label.substr(0, 7) + '...';
|
|
}
|
|
that.blockList[thisBlock].text.text = label;
|
|
};
|
|
|
|
this.makeNewBlock('text', postProcess, [thisBlock, value]);
|
|
} else if (myBlock.docks[i + 1][2] === 'solfegein') {
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
var label = value.toString();
|
|
that.blockList[thisBlock].text.text = label;
|
|
};
|
|
|
|
this.makeNewBlock('solfege', postProcess, [thisBlock, value]);
|
|
} else if (myBlock.docks[i + 1][2] === 'notein') {
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
var label = value.toString();
|
|
that.blockList[thisBlock].text.text = label;
|
|
};
|
|
|
|
this.makeNewBlock('notename', postProcess, [thisBlock, value]);
|
|
} else if (myBlock.docks[i + 1][2] === 'mediain') {
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
if (value != null) {
|
|
// loadThumbnail(that, thisBlock, null);
|
|
}
|
|
};
|
|
|
|
this.makeNewBlock('media', postProcess, [thisBlock, value]);
|
|
} else if (myBlock.docks[i + 1][2] === 'filein') {
|
|
postProcess = function (blk) {
|
|
that.updateBlockText(blk);
|
|
}
|
|
this.makeNewBlock('loadFile', postProcess, thisBlock);
|
|
} else {
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
that.blockList[thisBlock].text.text = value.toString();
|
|
};
|
|
|
|
this.makeNewBlock('number', postProcess, [thisBlock, value]);
|
|
}
|
|
|
|
var myConnectionBlock = this.blockList[cblk + i];
|
|
myConnectionBlock.connections = [blk];
|
|
myConnectionBlock.value = value;
|
|
myBlock.connections[i + 1] = cblk + i;
|
|
}
|
|
|
|
// Generate and position the block bitmaps and labels
|
|
this.updateBlockPositions();
|
|
// console.log('Adjust Docks: ' + this.blockList[blk].name);
|
|
this.adjustDocks(blk, true);
|
|
this.refreshCanvas();
|
|
|
|
return blk;
|
|
};
|
|
|
|
this.findDragGroup = function (blk) {
|
|
// Generate a drag group from blocks connected to blk
|
|
this.dragLoopCounter = 0;
|
|
this.dragGroup = [];
|
|
this._calculateDragGroup(blk);
|
|
};
|
|
|
|
this._calculateDragGroup = function (blk) {
|
|
// Give a block, find all the blocks connected to it
|
|
this.dragLoopCounter += 1;
|
|
if (this.dragLoopCount > this.blockList.length) {
|
|
console.log('maximum loop counter exceeded in calculateDragGroup... this is bad. ' + blk);
|
|
return;
|
|
}
|
|
|
|
if (blk == null) {
|
|
console.log('null block passed to calculateDragGroup');
|
|
return;
|
|
}
|
|
|
|
var myBlock = this.blockList[blk];
|
|
// If this happens, something is really broken.
|
|
if (myBlock == null) {
|
|
console.log('null block encountered... this is bad. ' + blk);
|
|
return;
|
|
}
|
|
|
|
// As before, does these ever happen?
|
|
if (myBlock.connections == null) {
|
|
this.dragGroup = [blk];
|
|
return;
|
|
}
|
|
|
|
// Some malformed blocks might have no connections.
|
|
if (myBlock.connections.length === 0) {
|
|
this.dragGroup = [blk];
|
|
return;
|
|
}
|
|
|
|
this.dragGroup.push(blk);
|
|
|
|
for (var c = 1; c < myBlock.connections.length; c++) {
|
|
var cblk = myBlock.connections[c];
|
|
if (cblk != null) {
|
|
// Recurse
|
|
this._calculateDragGroup(cblk);
|
|
}
|
|
}
|
|
};
|
|
|
|
this.setActionProtoVisiblity = function (state) {
|
|
// By default, the nameddo protoblock is hidden.
|
|
var actionsPalette = this.palettes.dict['action'];
|
|
var stateChanged = false;
|
|
for (var blockId = 0; blockId < actionsPalette.protoList.length; blockId++) {
|
|
var block = actionsPalette.protoList[blockId];
|
|
if ('nameddo' === block.name && block.defaults.length === 0) {
|
|
if (block.hidden === state) {
|
|
block.hidden = !state;
|
|
stateChanged = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Force an update if the name has changed.
|
|
if (stateChanged) {
|
|
this.palettes.hide();
|
|
this.palettes.updatePalettes('action');
|
|
this.palettes.show();
|
|
}
|
|
};
|
|
|
|
this.findUniqueActionName = function (name) {
|
|
// If we have a stack named 'action', make the protoblock visible.
|
|
if (name === _('action')) {
|
|
this.setActionProtoVisiblity(true);
|
|
}
|
|
|
|
// Make sure we don't make two actions with the same name.
|
|
var actionNames = [];
|
|
for (var blk = 0; blk < this.blockList.length; blk++) {
|
|
if ((this.blockList[blk].name === 'text' || this.blockList[blk].name === 'string') && !this.blockList[blk].trash) {
|
|
var c = this.blockList[blk].connections[0];
|
|
if (c != null && this.blockList[c].name === 'action' && !this.blockList[c].trash) {
|
|
actionNames.push(this.blockList[blk].value);
|
|
}
|
|
}
|
|
}
|
|
|
|
var i = 1;
|
|
var value = name;
|
|
while (actionNames.indexOf(value) !== -1) {
|
|
value = name + i.toString();
|
|
i += 1;
|
|
}
|
|
return value;
|
|
};
|
|
|
|
this._findDrumURLs = function () {
|
|
// Make sure we initialize any drum with a URL name.
|
|
for (var blk = 0; blk < this.blockList.length; blk++) {
|
|
if (this.blockList[blk].name === 'text' || this.blockList[blk].name === 'string') {
|
|
var c = this.blockList[blk].connections[0];
|
|
if (c != null && ['playdrum', 'setdrum', 'setvoice'].indexOf(this.blockList[c].name) !== -1) {
|
|
if (this.blockList[blk].value.slice(0, 4) === 'http') {
|
|
if (_THIS_IS_MUSIC_BLOCKS_) {
|
|
this.logo.synth.loadSynth(this.blockList[blk].value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
this.renameBoxes = function (oldName, newName) {
|
|
if (oldName === newName) {
|
|
return;
|
|
}
|
|
for (var blk = 0; blk < this.blockList.length; blk++) {
|
|
if (this.blockList[blk].name === 'text') {
|
|
var c = this.blockList[blk].connections[0];
|
|
if (c != null && this.blockList[c].name === 'box') {
|
|
if (this.blockList[blk].value === oldName) {
|
|
this.blockList[blk].value = newName;
|
|
this.blockList[blk].text.text = newName;
|
|
try {
|
|
this.blockList[blk].container.updateCache();
|
|
} catch (e) {
|
|
console.log(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
this.renameNamedboxes = function (oldName, newName) {
|
|
if (oldName === newName) {
|
|
return;
|
|
}
|
|
|
|
for (var blk = 0; blk < this.blockList.length; blk++) {
|
|
if (this.blockList[blk].name === 'namedbox') {
|
|
if (this.blockList[blk].privateData === oldName) {
|
|
this.blockList[blk].privateData = newName;
|
|
this.blockList[blk].overrideName = newName;
|
|
this.blockList[blk].regenerateArtwork();
|
|
// Update label...
|
|
try {
|
|
this.blockList[blk].container.updateCache();
|
|
} catch (e) {
|
|
console.log(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the palette
|
|
var blockPalette = this.palettes.dict['boxes'];
|
|
var nameChanged = false;
|
|
for (var blockId = 0; blockId < blockPalette.protoList.length; blockId++) {
|
|
var block = blockPalette.protoList[blockId];
|
|
if (block.name === 'namedbox' && block.defaults[0] !== _('box') && block.defaults[0] === oldName) {
|
|
// console.log('renaming ' + block.defaults[0] + ' to ' + newName);
|
|
block.defaults[0] = newName;
|
|
nameChanged = true;
|
|
}
|
|
}
|
|
|
|
// Force an update if the name has changed.
|
|
if (nameChanged) {
|
|
this.palettes.hide();
|
|
this.palettes.updatePalettes('boxes');
|
|
this.palettes.show();
|
|
}
|
|
};
|
|
|
|
this.renameDos = function (oldName, newName, skipBlock) {
|
|
if (oldName === newName) {
|
|
return;
|
|
}
|
|
|
|
// Update the blocks, do->oldName should be do->newName
|
|
// Named dos are modified in a separate function below.
|
|
for (var blk = 0; blk < this.blockList.length; blk++) {
|
|
if (blk === skipBlock) {
|
|
continue;
|
|
}
|
|
|
|
var myBlock = this.blockList[blk];
|
|
var blkParent = this.blockList[myBlock.connections[0]];
|
|
if (blkParent == null) {
|
|
continue;
|
|
}
|
|
|
|
if (['do', 'calc', 'doArg', 'calcArg', 'action'].indexOf(blkParent.name) === -1) {
|
|
continue;
|
|
}
|
|
|
|
var blockValue = myBlock.value;
|
|
if (blockValue === oldName) {
|
|
myBlock.value = newName;
|
|
var label = myBlock.value;
|
|
if (label.length > 8) {
|
|
label = label.substr(0, 7) + '...';
|
|
}
|
|
myBlock.text.text = label;
|
|
myBlock.container.updateCache();
|
|
}
|
|
}
|
|
};
|
|
|
|
this.renameNameddos = function (oldName, newName) {
|
|
if (oldName === newName) {
|
|
return;
|
|
}
|
|
|
|
// Update the blocks, do->oldName should be do->newName
|
|
for (var blk = 0; blk < this.blockList.length; blk++) {
|
|
if (['nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg'].indexOf(this.blockList[blk].name) !== -1) {
|
|
if (this.blockList[blk].privateData === oldName) {
|
|
this.blockList[blk].privateData = newName;
|
|
var label = newName;
|
|
if (label.length > 8) {
|
|
label = label.substr(0, 7) + '...';
|
|
}
|
|
|
|
this.blockList[blk].overrideName = label;
|
|
// console.log('regenerating artwork for ' + this.blockList[blk].name + ' block[' + blk + ']: ' + oldName + ' -> ' + label);
|
|
this.blockList[blk].regenerateArtwork();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the palette
|
|
var actionsPalette = this.palettes.dict['action'];
|
|
var nameChanged = false;
|
|
for (var blockId = 0; blockId < actionsPalette.protoList.length; blockId++) {
|
|
var block = actionsPalette.protoList[blockId];
|
|
if (['nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg'].indexOf(block.name) !== -1 /* && block.defaults[0] !== _('action') */ && block.defaults[0] === oldName) {
|
|
// console.log('renaming ' + block.name + ': ' + block.defaults[0] + ' to ' + newName);
|
|
block.defaults[0] = newName;
|
|
nameChanged = true;
|
|
}
|
|
}
|
|
|
|
// Force an update if the name has changed.
|
|
if (nameChanged) {
|
|
this.palettes.hide();
|
|
this.palettes.updatePalettes('action');
|
|
this.palettes.show();
|
|
}
|
|
};
|
|
|
|
this.newStoreinBlock = function (name) {
|
|
if (name == null) {
|
|
console.log('null name passed to newStoreinBlock');
|
|
return;
|
|
} else if (name == undefined) {
|
|
console.log('undefined name passed to newStoreinBlock');
|
|
return;
|
|
} else if ('myStorein_' + name in this.protoBlockDict) {
|
|
// console.log(name + ' already in palette');
|
|
return;
|
|
}
|
|
|
|
// console.log('new storein block ' + name);
|
|
var myStoreinBlock = new ProtoBlock('storein');
|
|
this.protoBlockDict['myStorein_' + name] = myStoreinBlock;
|
|
myStoreinBlock.palette = this.palettes.dict['boxes'];
|
|
myStoreinBlock.defaults.push(name);
|
|
myStoreinBlock.defaults.push(NUMBERBLOCKDEFAULT);
|
|
myStoreinBlock.staticLabels.push(_('store in'), _('name'), _('value'));
|
|
myStoreinBlock.adjustWidthToLabel();
|
|
myStoreinBlock.twoArgBlock();
|
|
myStoreinBlock.dockTypes[1] = 'anyin';
|
|
myStoreinBlock.dockTypes[2] = 'anyin';
|
|
if (name === 'box') {
|
|
return;
|
|
}
|
|
|
|
// Add the new block to the top of the palette.
|
|
myStoreinBlock.palette.add(myStoreinBlock, true);
|
|
};
|
|
|
|
this.newNamedboxBlock = function (name) {
|
|
if (name == null) {
|
|
console.log('null name passed to newNamedboxBlock');
|
|
return;
|
|
} else if (name == undefined) {
|
|
console.log('undefined name passed to newNamedboxBlock');
|
|
return;
|
|
} else if ('myBox_' + name in this.protoBlockDict) {
|
|
return;
|
|
}
|
|
|
|
var myBoxBlock = new ProtoBlock('namedbox');
|
|
this.protoBlockDict['myBox_' + name] = myBoxBlock;
|
|
myBoxBlock.palette = this.palettes.dict['boxes'];
|
|
myBoxBlock.defaults.push(name);
|
|
myBoxBlock.staticLabels.push(name);
|
|
myBoxBlock.parameterBlock();
|
|
if (name === 'box') {
|
|
return;
|
|
}
|
|
|
|
// Add the new block to the top of the palette.
|
|
myBoxBlock.palette.add(myBoxBlock, true);
|
|
};
|
|
|
|
this._newLocalArgBlock = function (name) {
|
|
// name === 1, 2, 3, ...
|
|
var blkname = 'arg_' + name;
|
|
if ('myArg_' + name in this.protoBlockDict) {
|
|
return;
|
|
}
|
|
|
|
if (blkname in this.protoBlockDict) {
|
|
return;
|
|
}
|
|
|
|
var myNamedArgBlock = new ProtoBlock('namedarg');
|
|
this.protoBlockDict['myArg_' + blkname] = myNamedArgBlock;
|
|
myNamedArgBlock.palette = this.palettes.dict['action'];
|
|
myNamedArgBlock.defaults.push(name);
|
|
myNamedArgBlock.staticLabels.push('arg ' + name);
|
|
myNamedArgBlock.parameterBlock();
|
|
|
|
if (blkname === 'arg_1') {
|
|
return;
|
|
}
|
|
|
|
myNamedArgBlock.palette.add(myNamedArgBlock, true);
|
|
|
|
// Force regeneration of palette after adding new block.
|
|
// Add delay to avoid race condition.
|
|
var that = this;
|
|
setTimeout(function () {
|
|
that.palettes.hide();
|
|
that.palettes.updatePalettes('action');
|
|
that.palettes.show();
|
|
}, 500);
|
|
};
|
|
|
|
this._removeNamedoEntries = function (name) {
|
|
// Delete any old palette entries.
|
|
// console.log('DELETE: removing old palette entries for ' + name);
|
|
if (this.protoBlockDict['myDo_' + name]) {
|
|
// console.log('deleting myDo_' + name + ' ' + this.protoBlockDict['myDo_' + name].name);
|
|
this.protoBlockDict['myDo_' + name].hide = true;
|
|
delete this.protoBlockDict['myDo_' + name];
|
|
} else if (this.protoBlockDict['myCalc_' + name]) {
|
|
// console.log('deleting myCalc_' + name + ' ' + this.protoBlockDict['myCalc_' + name].name);
|
|
this.protoBlockDict['myCalc_' + name].hide = true;
|
|
delete this.protoBlockDict['myCalc_' + name];
|
|
} else if (this.protoBlockDict['myDoArg_' + name]) {
|
|
// console.log('deleting myDoArg_' + name + ' ' + this.protoBlockDict['myDoArg_' + name].name);
|
|
this.protoBlockDict['myDoArg_' + name].hide = true;
|
|
delete this.protoBlockDict['myDoArg_' + name];
|
|
} else if (this.protoBlockDict['myCalcArg_' + name]) {
|
|
// console.log('deleting myCalcArg_' + name + ' ' + this.protoBlockDict['myCalcArg_' + name].name);
|
|
this.protoBlockDict['myCalcArg_' + name].hide = true;
|
|
delete this.protoBlockDict['myCalcArg_' + name];
|
|
}
|
|
};
|
|
|
|
this.newNameddoBlock = function (name, hasReturn, hasArgs) {
|
|
// Depending upon the form of the associated action block, we
|
|
// want to add a named do, a named calc, a named do w/args, or
|
|
// a named calc w/args.
|
|
if (name === _('action')) {
|
|
// 'action' already has its associated palette entries.
|
|
return false;
|
|
}
|
|
|
|
if (hasReturn && hasArgs) {
|
|
this.newNamedcalcArgBlock(name);
|
|
return true;
|
|
} else if (!hasReturn && hasArgs) {
|
|
this.newNameddoArgBlock(name);
|
|
return true;
|
|
} else if (hasReturn && !hasArgs) {
|
|
this.newNamedcalcBlock(name);
|
|
return true;
|
|
} else if (this.protoBlockDict['myDo_' + name] === undefined) {
|
|
var myDoBlock = new ProtoBlock('nameddo');
|
|
this.protoBlockDict['myDo_' + name] = myDoBlock;
|
|
myDoBlock.palette = this.palettes.dict['action'];
|
|
myDoBlock.defaults.push(name);
|
|
myDoBlock.staticLabels.push(name);
|
|
myDoBlock.zeroArgBlock();
|
|
myDoBlock.palette.add(myDoBlock, true);
|
|
this.palettes.updatePalettes();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
this.newNamedcalcBlock = function (name) {
|
|
if (this.protoBlockDict['myCalc_' + name] === undefined) {
|
|
// console.log('creating myCalc_' + name);
|
|
var myCalcBlock = new ProtoBlock('namedcalc');
|
|
this.protoBlockDict['myCalc_' + name] = myCalcBlock;
|
|
myCalcBlock.palette = this.palettes.dict['action'];
|
|
myCalcBlock.defaults.push(name);
|
|
myCalcBlock.staticLabels.push(name);
|
|
myCalcBlock.zeroArgBlock();
|
|
// Add the new block to the top of the palette.
|
|
myCalcBlock.palette.add(myCalcBlock, true);
|
|
// } else {
|
|
// console.log('myCalc_' + name + ' already exists.');
|
|
}
|
|
};
|
|
|
|
this.newNameddoArgBlock = function (name) {
|
|
if (this.protoBlockDict['myDoArg_' + name] === undefined) {
|
|
// console.log('creating myDoArg_' + name);
|
|
var myDoArgBlock = new ProtoBlock('nameddoArg');
|
|
this.protoBlockDict['myDoArg_' + name] = myDoArgBlock;
|
|
myDoArgBlock.palette = this.palettes.dict['action'];
|
|
myDoArgBlock.defaults.push(name);
|
|
myDoArgBlock.staticLabels.push(name);
|
|
myDoArgBlock.zeroArgBlock();
|
|
// Add the new block to the top of the palette.
|
|
myDoArgBlock.palette.add(myDoArgBlock, true);
|
|
// } else {
|
|
// console.log('myDoArg_' + name + ' already exists.');
|
|
}
|
|
};
|
|
|
|
this.newNamedcalcArgBlock = function (name) {
|
|
if (this.protoBlockDict['myCalcArg_' + name] === undefined) {
|
|
// console.log('creating myCalcArg_' + name);
|
|
var myCalcArgBlock = new ProtoBlock('namedcalcArg');
|
|
this.protoBlockDict['myCalcArg_' + name] = myCalcArgBlock;
|
|
myCalcArgBlock.palette = this.palettes.dict['action'];
|
|
myCalcArgBlock.defaults.push(name);
|
|
myCalcArgBlock.staticLabels.push(name);
|
|
myCalcArgBlock.zeroArgBlock();
|
|
// Add the new block to the top of the palette.
|
|
myCalcArgBlock.palette.add(myCalcArgBlock, true);
|
|
// } else {
|
|
// console.log('myCalcArg_' + name + ' already exists.');
|
|
}
|
|
};
|
|
|
|
this._insideArgClamp = function (blk) {
|
|
// Returns a containing arg clamp block or null
|
|
if (this.blockList[blk] == null) {
|
|
// race condition?
|
|
console.log('null block in blockList? ' + blk);
|
|
return null;
|
|
} else if (this.blockList[blk].connections[0] == null) {
|
|
return null;
|
|
} else {
|
|
var cblk = this.blockList[blk].connections[0];
|
|
if (this.blockList[cblk].isArgClamp()) {
|
|
return cblk;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
};
|
|
|
|
this._insideExpandableBlock = function (blk) {
|
|
// Returns a containing expandable block or null
|
|
if (this.blockList[blk] == null) {
|
|
// race condition?
|
|
console.log('null block in blockList? ' + blk);
|
|
return null;
|
|
} else if (this.blockList[blk].connections[0] == null) {
|
|
return null;
|
|
} else {
|
|
var cblk = this.blockList[blk].connections[0];
|
|
if (this.blockList[cblk].isExpandableBlock()) {
|
|
// If it is the last connection, keep searching.
|
|
if (this.blockList[cblk].isArgFlowClampBlock()) {
|
|
return cblk;
|
|
} else if (blk === last(this.blockList[cblk].connections)) {
|
|
return this._insideExpandableBlock(cblk);
|
|
} else {
|
|
return cblk;
|
|
}
|
|
} else {
|
|
return this._insideExpandableBlock(cblk);
|
|
}
|
|
}
|
|
};
|
|
|
|
this._insideNoteBlock = function (blk) {
|
|
// Returns a containing note block or null
|
|
if (this.blockList[blk] == null) {
|
|
console.log('null block in blockList? ' + blk);
|
|
return null;
|
|
} else if (this.blockList[blk].connections[0] == null) {
|
|
return null;
|
|
} else {
|
|
var cblk = this.blockList[blk].connections[0];
|
|
if (this.blockList[cblk].isExpandableBlock()) {
|
|
// If it is the last connection, keep searching.
|
|
if (blk === last(this.blockList[cblk].connections)) {
|
|
return this._insideNoteBlock(cblk);
|
|
} else if (blk === this.blockList[cblk].connections[1]) {
|
|
// Connection 1 of a note block is not inside the clamp.
|
|
return null;
|
|
} else {
|
|
if (['newnote', 'osctime'].indexOf(this.blockList[cblk].name) !== -1) {
|
|
return cblk;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
} else {
|
|
return this._insideNoteBlock(cblk);
|
|
}
|
|
}
|
|
};
|
|
|
|
this.triggerLongPress = function (myBlock) {
|
|
this.timeOut == null;
|
|
this.inLongPress = true;
|
|
var z = this.stage.getNumChildren() - 1;
|
|
|
|
// Auto-select stack for copying -- no need to actually click on
|
|
// the copy button.
|
|
var topBlock = this.findTopBlock(this.activeBlock);
|
|
this.selectedStack = topBlock;
|
|
|
|
// Copy the selectedStack.
|
|
this.selectedBlocksObj = JSON.parse(JSON.stringify(this._copyBlocksToObj()));
|
|
|
|
this.updatePasteButton();
|
|
|
|
if (myBlock.name === 'action') {
|
|
this.dismissButton.visible = true;
|
|
this.dismissButton.x = myBlock.container.x - 27;
|
|
this.dismissButton.y = myBlock.container.y - 27;
|
|
this.stage.setChildIndex(this.dismissButton, z - 1);
|
|
this.saveStackButton.visible = true;
|
|
this.saveStackButton.x = myBlock.container.x + 27;
|
|
this.saveStackButton.y = myBlock.container.y - 27;
|
|
this.stage.setChildIndex(this.saveStackButton, z - 2);
|
|
}
|
|
|
|
this.refreshCanvas();
|
|
};
|
|
|
|
this.pasteStack = function () {
|
|
// Copy a stack of blocks by creating a blockObjs and passing
|
|
// it to this.load.
|
|
if (this.selectedStack == null) {
|
|
return;
|
|
}
|
|
|
|
// First, hide the palettes as they will need updating.
|
|
for (var name in this.palettes.dict) {
|
|
this.palettes.dict[name].hideMenu(true);
|
|
}
|
|
|
|
// var blockObjs = this._copyBlocksToObj();
|
|
// this.loadNewBlocks(blockObjs);
|
|
// console.log(this.selectedBlocksObj);
|
|
this.loadNewBlocks(this.selectedBlocksObj);
|
|
};
|
|
|
|
this.saveStack = function () {
|
|
// Save a stack of blocks to local storage and the 'myblocks'
|
|
// palette by creating a blockObjs and ...
|
|
if (this.selectedStack == null) {
|
|
return;
|
|
}
|
|
|
|
this.palettes.hide();
|
|
|
|
var blockObjs = this._copyBlocksToObj();
|
|
// The first block is an action block. Its first connection is
|
|
// the block containing its label.
|
|
var nameBlk = blockObjs[0][4][1];
|
|
|
|
if (nameBlk == null) {
|
|
console.log('action not named... skipping');
|
|
} else {
|
|
console.log(blockObjs[nameBlk][1][1]);
|
|
if (typeof(blockObjs[nameBlk][1][1]) === 'string') {
|
|
var name = blockObjs[nameBlk][1][1];
|
|
} else if (typeof(blockObjs[nameBlk][1][1]) === 'number') {
|
|
var name = blockObjs[nameBlk][1][1].toString();
|
|
} else {
|
|
var name = blockObjs[nameBlk][1][1]['value'];
|
|
}
|
|
storage.macros = prepareMacroExports(name, blockObjs, this.macroDict);
|
|
if (sugarizerCompatibility.isInsideSugarizer()) {
|
|
sugarizerCompatibility.saveLocally(function () {
|
|
this.addToMyPalette(name, blockObjs);
|
|
// this.palettes.updatePalettes('myblocks');
|
|
});
|
|
} else {
|
|
this.addToMyPalette(name, blockObjs);
|
|
// this.palettes.updatePalettes('myblocks');
|
|
}
|
|
}
|
|
};
|
|
|
|
this._copyBlocksToObj = function () {
|
|
var blockObjs = [];
|
|
var blockMap = {};
|
|
|
|
this.findDragGroup(this.selectedStack);
|
|
for (var b = 0; b < this.dragGroup.length; b++) {
|
|
myBlock = this.blockList[this.dragGroup[b]];
|
|
if (b === 0) {
|
|
x = 75 - this.stage.x;
|
|
y = 75 - this.stage.y;
|
|
} else {
|
|
x = 0;
|
|
y = 0;
|
|
}
|
|
|
|
if (myBlock.isValueBlock()) {
|
|
switch (myBlock.name) {
|
|
case 'media':
|
|
blockItem = [b, [myBlock.name, null], x, y, []];
|
|
break;
|
|
default:
|
|
blockItem = [b, [myBlock.name, myBlock.value], x, y, []];
|
|
break;
|
|
}
|
|
} else if (['namedbox', 'nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg', 'namedarg'].indexOf(myBlock.name) !== -1) {
|
|
blockItem = [b, [myBlock.name, {'value': myBlock.privateData}], x, y, []];
|
|
} else {
|
|
blockItem = [b, myBlock.name, x, y, []];
|
|
}
|
|
|
|
blockMap[this.dragGroup[b]] = b;
|
|
blockObjs.push(blockItem);
|
|
}
|
|
for (var b = 0; b < this.dragGroup.length; b++) {
|
|
myBlock = this.blockList[this.dragGroup[b]];
|
|
for (var c = 0; c < myBlock.connections.length; c++) {
|
|
if (myBlock.connections[c] == null) {
|
|
blockObjs[b][4].push(null);
|
|
} else {
|
|
blockObjs[b][4].push(blockMap[myBlock.connections[c]]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return blockObjs;
|
|
};
|
|
|
|
this.addToMyPalette = function (name, obj) {
|
|
// On the palette we store the macro as a basic block.
|
|
var myBlock = new ProtoBlock('macro_' + name);
|
|
var blkName = 'macro_' + name;
|
|
this.protoBlockDict[blkName] = myBlock;
|
|
if (!('myblocks' in this.palettes.dict)) {
|
|
this.palettes.add('myblocks');
|
|
}
|
|
|
|
myBlock.palette = this.palettes.dict['myblocks'];
|
|
myBlock.zeroArgBlock();
|
|
myBlock.staticLabels.push(_(name));
|
|
this.protoBlockDict[blkName].palette.add(this.protoBlockDict[blkName]);
|
|
};
|
|
|
|
this.loadNewBlocks = function (blockObjs) {
|
|
// Check for blocks connected to themselves,
|
|
// and for action blocks not connected to text blocks.
|
|
for (var b = 0; b < blockObjs.length; b++) {
|
|
var blkData = blockObjs[b];
|
|
for (var c in blkData[4]) {
|
|
if (blkData[4][c] === blkData[0]) {
|
|
console.log('Circular connection in block data: ' + blkData);
|
|
console.log('Punting loading of new blocks!');
|
|
console.log(blockObjs);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// We'll need a list of existing storein and action names.
|
|
var currentActionNames = [];
|
|
var currentStoreinNames = [];
|
|
for (var b = 0; b < this.blockList.length; b++) {
|
|
if (this.blockList[b].trash) {
|
|
continue;
|
|
}
|
|
|
|
if (this.blockList[b].name === 'action') {
|
|
if (this.blockList[b].connections[1] != null) {
|
|
console.log(this.blockList[this.blockList[b].connections[1]].value);
|
|
currentActionNames.push(this.blockList[this.blockList[b].connections[1]].value);
|
|
}
|
|
} else if (this.blockList[b].name === 'storein') {
|
|
if (this.blockList[b].connections[1] != null) {
|
|
currentStoreinNames.push(this.blockList[this.blockList[b].connections[1]].value);
|
|
}
|
|
}
|
|
}
|
|
|
|
// We need to track two-arg blocks in case they need expanding.
|
|
this._checkTwoArgBlocks = [];
|
|
|
|
// And arg clamp blocks in case they need expanding.
|
|
this._checkArgClampBlocks = [];
|
|
|
|
// Don't make duplicate action names.
|
|
// Add a palette entry for any new storein blocks.
|
|
var stringNames = [];
|
|
var stringValues = {}; // label: [blocks with that label]
|
|
var actionNames = {}; // action block: label block
|
|
var storeinNames = {}; // storein block: label block
|
|
var doNames = {}; // do block: label block, nameddo block value
|
|
|
|
// action and start blocks that need to be collapsed.
|
|
this.blocksToCollapse = [];
|
|
|
|
// Scan for any new action and storein blocks to identify
|
|
// duplicates. We also need to track start and action blocks
|
|
// that may need to be collapsed.
|
|
for (var b = 0; b < blockObjs.length; b++) {
|
|
var blkData = blockObjs[b];
|
|
// blkData[1] could be a string or an object.
|
|
if (typeof(blkData[1]) === 'string') {
|
|
var name = blkData[1];
|
|
} else {
|
|
var name = blkData[1][0];
|
|
}
|
|
|
|
if (!(name in this.protoBlockDict)) {
|
|
switch (name) {
|
|
case 'hat':
|
|
name = 'action';
|
|
break;
|
|
case 'string':
|
|
name = 'text';
|
|
break;
|
|
default:
|
|
console.log('skipping ' + name);
|
|
continue;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (['arg', 'twoarg'].indexOf(this.protoBlockDict[name].style) !== -1) {
|
|
if (this.protoBlockDict[name].expandable) {
|
|
this._checkTwoArgBlocks.push(this.blockList.length + b);
|
|
}
|
|
}
|
|
|
|
// FIXME: Use tests in block.js
|
|
if (['clamp', 'argclamp', 'argclamparg', 'doubleclamp', 'argflowclamp'].indexOf(this.protoBlockDict[name].style) !== -1) {
|
|
this._checkArgClampBlocks.push(this.blockList.length + b);
|
|
}
|
|
|
|
switch (name) {
|
|
case 'text':
|
|
var key = blkData[1][1];
|
|
if (stringValues[key] === undefined) {
|
|
stringValues[key] = [];
|
|
}
|
|
stringValues[key].push(b);
|
|
break;
|
|
case 'action':
|
|
case 'hat':
|
|
if (blkData[4][1] != null) {
|
|
actionNames[b] = blkData[4][1];
|
|
}
|
|
break;
|
|
case 'storein':
|
|
if (blkData[4][1] != null) {
|
|
storeinNames[b] = blkData[4][1];
|
|
}
|
|
break;
|
|
case 'nameddo':
|
|
case 'namedcalc':
|
|
case 'nameddoArg':
|
|
case 'namedcalcArg':
|
|
doNames[b] = blkData[1][1]['value'];
|
|
break;
|
|
case 'do':
|
|
case 'stack':
|
|
if (blkData[4][1] != null) {
|
|
doNames[b] = blkData[4][1];
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (name) {
|
|
case 'action':
|
|
case 'pitchdrummatrix':
|
|
case 'rhythmruler':
|
|
case 'pitchstaircase':
|
|
case 'tempo':
|
|
case 'pitchslider':
|
|
case 'matrix':
|
|
case 'drum':
|
|
case 'status':
|
|
case 'start':
|
|
if (typeof(blkData[1]) === 'object' && blkData[1].length > 1 && typeof(blkData[1][1]) === 'object' && 'collapsed' in blkData[1][1]) {
|
|
if (blkData[1][1]['collapsed']) {
|
|
this.blocksToCollapse.push(this.blockList.length + b);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
var updatePalettes = false;
|
|
// Make sure new storein names have palette entries.
|
|
for (var b in storeinNames) {
|
|
var blkData = blockObjs[storeinNames[b]];
|
|
if (currentStoreinNames.indexOf(blkData[1][1]) === -1) {
|
|
if (typeof(blkData[1][1]) === 'string') {
|
|
var name = blkData[1][1];
|
|
} else {
|
|
var name = blkData[1][1]['value'];
|
|
}
|
|
// console.log('Adding new palette entries for store-in ' + name);
|
|
this.newStoreinBlock(name);
|
|
this.newNamedboxBlock(name);
|
|
updatePalettes = true;
|
|
}
|
|
}
|
|
|
|
// Make sure action names are unique.
|
|
for (var b in actionNames) {
|
|
// Is there a proto do block with this name? If so, find a
|
|
// new name.
|
|
// Name = the value of the connected label.
|
|
var blkData = blockObjs[actionNames[b]];
|
|
if (typeof(blkData[1][1]) === 'string') {
|
|
var name = blkData[1][1];
|
|
} else {
|
|
var name = blkData[1][1]['value'];
|
|
}
|
|
|
|
// If we have a stack named 'action', make the protoblock visible.
|
|
if (name === _('action')) {
|
|
this.setActionProtoVisiblity(true);
|
|
}
|
|
var oldName = name;
|
|
var i = 1;
|
|
while (currentActionNames.indexOf(name) !== -1) {
|
|
name = oldName + i.toString();
|
|
i += 1;
|
|
// Should never happen... but just in case.
|
|
if (i > this.blockList.length) {
|
|
console.log('Could not generate unique action name.');
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (oldName !== name) {
|
|
// Change the name of the action...
|
|
console.log('action ' + oldName + ' is being renamed ' + name);
|
|
blkData[1][1] = {'value': name};
|
|
}
|
|
|
|
// and any do blocks
|
|
for (var d in doNames) {
|
|
var thisBlkData = blockObjs[d];
|
|
if (typeof(thisBlkData[1]) === 'string') {
|
|
var blkName = thisBlkData[1];
|
|
} else {
|
|
var blkName = thisBlkData[1][0];
|
|
}
|
|
if (['nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg'].indexOf(blkName) !== -1) {
|
|
if (thisBlkData[1][1]['value'] === oldName) {
|
|
thisBlkData[1][1] = {'value': name};
|
|
}
|
|
} else {
|
|
var doBlkData = blockObjs[doNames[d]];
|
|
if (typeof(doBlkData[1][1]) === 'string') {
|
|
if (doBlkData[1][1] === oldName) {
|
|
// console.log('renaming ' + oldName + ' to ' + name);
|
|
doBlkData[1][1] = name;
|
|
}
|
|
} else {
|
|
if (doBlkData[1][1]['value'] === oldName) {
|
|
// console.log('renaming ' + oldName + ' to ' + name);
|
|
doBlkData[1][1] = {'value': name};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (updatePalettes) {
|
|
this.palettes.hide();
|
|
this.palettes.updatePalettes('action');
|
|
this.palettes.show();
|
|
}
|
|
|
|
|
|
// This section of the code attempts to repair imported
|
|
// code. For example, it adds missing hidden blocks and
|
|
// convert old-style notes to new-style notes.
|
|
blockObjsLength = blockObjs.length;
|
|
var extraBlocksLength = 0;
|
|
|
|
for (var b = 0; b < blockObjsLength; b++) {
|
|
if (typeof(blockObjs[b][1]) === 'object') {
|
|
var name = blockObjs[b][1][0];
|
|
} else {
|
|
var name = blockObjs[b][1];
|
|
}
|
|
|
|
switch (name) {
|
|
case 'articulation':
|
|
case 'augmented':
|
|
case 'backward':
|
|
case 'crescendo':
|
|
case 'diminished':
|
|
case 'dividebeatfactor':
|
|
case 'drift':
|
|
case 'duplicatenotes':
|
|
case 'invert1':
|
|
case 'fill':
|
|
case 'flat':
|
|
case 'hollowline':
|
|
case 'major':
|
|
case 'minor':
|
|
case 'multiplybeatfactor':
|
|
case 'note':
|
|
case 'newnote':
|
|
case 'newslur':
|
|
case 'newstaccato':
|
|
case 'newswing':
|
|
case 'newswing2':
|
|
case 'osctime':
|
|
case 'perfect':
|
|
case 'pluck':
|
|
case 'rhythmicdot':
|
|
case 'setbpm':
|
|
case 'setnotevolume2':
|
|
case 'settransposition':
|
|
case 'setvoice':
|
|
case 'sharp':
|
|
case 'skipnotes':
|
|
case 'slur':
|
|
case 'staccato':
|
|
case 'swing':
|
|
case 'tie':
|
|
case 'tuplet2':
|
|
case 'vibrato':
|
|
var len = blockObjs[b][4].length;
|
|
if (last(blockObjs[b][4]) == null) {
|
|
// If there is no next block, add a hidden block;
|
|
console.log('last connection of ' + name + ' is null: adding hidden block');
|
|
blockObjs[b][4][len - 1] = blockObjsLength + extraBlocksLength;
|
|
blockObjs.push([blockObjsLength + extraBlocksLength, 'hidden', 0, 0, [b, null]]);
|
|
extraBlocksLength += 1;
|
|
} else {
|
|
var nextBlock = blockObjs[b][4][len - 1];
|
|
|
|
if (typeof(blockObjs[nextBlock][1]) === 'object') {
|
|
var nextName = blockObjs[nextBlock][1][0];
|
|
} else {
|
|
var nextName = blockObjs[nextBlock][1];
|
|
}
|
|
|
|
if (nextName !== 'hidden') {
|
|
console.log('last connection of ' + name + ' is ' + nextName + ': adding hidden block');
|
|
// If the next block is not a hidden block, add one.
|
|
blockObjs[b][4][len - 1] = blockObjsLength + extraBlocksLength;
|
|
blockObjs[nextBlock][4][0] = blockObjsLength + extraBlocksLength;
|
|
blockObjs.push([blockObjsLength + extraBlocksLength, 'hidden', 0, 0, [b, nextBlock]]);
|
|
extraBlocksLength += 1;
|
|
}
|
|
}
|
|
|
|
if (['note', 'slur', 'staccato', 'swing'].indexOf(name) !== -1) {
|
|
// We need to convert to newnote style:
|
|
// (1) add a vspace to the start of the clamp of a note block.
|
|
console.log('note: ' + b);
|
|
var clampBlock = blockObjs[b][4][2];
|
|
blockObjs[b][4][2] = blockObjsLength + extraBlocksLength;
|
|
if (clampBlock == null) {
|
|
blockObjs.push([blockObjsLength + extraBlocksLength, 'vspace', 0, 0, [b, null]]);
|
|
} else {
|
|
blockObjs[clampBlock][4][0] = blockObjsLength + extraBlocksLength;
|
|
blockObjs.push([blockObjsLength + extraBlocksLength, 'vspace', 0, 0, [b, clampBlock]]);
|
|
}
|
|
|
|
extraBlocksLength += 1;
|
|
|
|
// (2) switch the first connection to divide 1 / arg.
|
|
var argBlock = blockObjs[b][4][1];
|
|
blockObjs[b][4][1] = blockObjsLength + extraBlocksLength;
|
|
if (argBlock == null) {
|
|
blockObjs.push([blockObjsLength + extraBlocksLength, 'divide', 0, 0, [b, blockObjsLength + extraBlocksLength + 1, blockObjsLength + extraBlocksLength + 2]]);
|
|
blockObjs.push([blockObjsLength + extraBlocksLength + 1, ['number', {'value': 1}], 0, 0, [blockObjsLength + extraBlocksLength]]);
|
|
blockObjs.push([blockObjsLength + extraBlocksLength + 2, ['number', {'value': 1}], 0, 0, [blockObjsLength + extraBlocksLength]]);
|
|
extraBlocksLength += 3;
|
|
} else {
|
|
blockObjs[argBlock][4][0] = blockObjsLength + extraBlocksLength;
|
|
blockObjs.push([blockObjsLength + extraBlocksLength, 'divide', 0, 0, [b, blockObjsLength + extraBlocksLength + 1, argBlock]]);
|
|
blockObjs.push([blockObjsLength + extraBlocksLength + 1, ['number', {'value': 1}], 0, 0, [blockObjsLength + extraBlocksLength]]);
|
|
extraBlocksLength += 2;
|
|
}
|
|
|
|
// (3) create a newnote block instead.
|
|
if (typeof(blockObjs[b][1]) === 'object') {
|
|
blockObjs[b][1][0] = 'new' + name;
|
|
} else {
|
|
blockObjs[b][1] = 'new' + name;
|
|
}
|
|
}
|
|
break;
|
|
case 'action':
|
|
// Ensure that there is a hidden block as the first
|
|
// block in the child flow (connection 2) of an action
|
|
// block (required to make the backward block function
|
|
// propperly).
|
|
var len = blockObjs[b][4].length;
|
|
if (blockObjs[b][4][2] == null) {
|
|
// If there is no child flow block, add a hidden block;
|
|
console.log('last connection of ' + name + ' is null: adding hidden block');
|
|
blockObjs[b][4][2] = blockObjsLength + extraBlocksLength;
|
|
blockObjs.push([blockObjsLength + extraBlocksLength, 'hidden', 0, 0, [b, null]]);
|
|
extraBlocksLength += 1;
|
|
} else {
|
|
var nextBlock = blockObjs[b][4][2];
|
|
|
|
if (typeof(blockObjs[nextBlock][1]) === 'object') {
|
|
var nextName = blockObjs[nextBlock][1][0];
|
|
} else {
|
|
var nextName = blockObjs[nextBlock][1];
|
|
}
|
|
|
|
if (nextName !== 'hidden') {
|
|
console.log('last connection of ' + name + ' is ' + nextName + ': adding hidden block');
|
|
// If the next block is not a hidden block, add one.
|
|
blockObjs[b][4][2] = blockObjsLength + extraBlocksLength;
|
|
blockObjs[nextBlock][4][0] = blockObjsLength + extraBlocksLength;
|
|
blockObjs.push([blockObjsLength + extraBlocksLength, 'hidden', 0, 0, [b, nextBlock]]);
|
|
extraBlocksLength += 1;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Append to the current set of blocks.
|
|
this._adjustTheseStacks = [];
|
|
this._adjustTheseDocks = [];
|
|
this._loadCounter = blockObjs.length;
|
|
|
|
// We add new blocks to the end of the block list.
|
|
var blockOffset = this.blockList.length;
|
|
var firstBlock = this.blockList.length;
|
|
|
|
for (var b = 0; b < this._loadCounter; b++) {
|
|
var thisBlock = blockOffset + b;
|
|
var blkData = blockObjs[b];
|
|
|
|
if (typeof(blkData[1]) === 'object') {
|
|
if (blkData[1].length === 1) {
|
|
var blkInfo = [blkData[1][0], {'value': null}];
|
|
} else if (['number', 'string'].indexOf(typeof(blkData[1][1])) !== -1) {
|
|
var blkInfo = [blkData[1][0], {'value': blkData[1][1]}];
|
|
if (COLLAPSABLES.indexOf(blkData[1][0]) !== -1) {
|
|
blkInfo[1]['collapsed'] = false;
|
|
}
|
|
} else {
|
|
var blkInfo = blkData[1];
|
|
}
|
|
} else {
|
|
var blkInfo = [blkData[1], {'value': null}];
|
|
if (COLLAPSABLES.indexOf(blkData[1]) !== -1) {
|
|
blkInfo[1]['collapsed'] = false;
|
|
}
|
|
}
|
|
|
|
var name = blkInfo[0];
|
|
|
|
var collapsed = false;
|
|
if (COLLAPSABLES.indexOf(name) !== -1) {
|
|
collapsed = blkInfo[1]['collapsed'];
|
|
}
|
|
|
|
if (blkInfo[1] == null) {
|
|
var value = null;
|
|
} else {
|
|
var value = blkInfo[1]['value'];
|
|
}
|
|
|
|
if (name in NAMEDICT) {
|
|
name = NAMEDICT[name];
|
|
}
|
|
|
|
var that = this;
|
|
|
|
// A few special cases.
|
|
switch (name) {
|
|
// Only add 'collapsed' arg to start, action blocks.
|
|
case 'start':
|
|
blkData[4][0] = null;
|
|
blkData[4][2] = null;
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var blkInfo = args[1];
|
|
that.blockList[thisBlock].value = that.turtles.turtleList.length;
|
|
that.turtles.addTurtle(that.blockList[thisBlock], blkInfo);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, blkInfo[1]], collapsed);
|
|
break;
|
|
case 'drum':
|
|
blkData[4][0] = null;
|
|
blkData[4][2] = null;
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var blkInfo = args[1];
|
|
that.blockList[thisBlock].value = that.turtles.turtleList.length;
|
|
that.turtles.addDrum(that.blockList[thisBlock], blkInfo);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, blkInfo[1]], collapsed);
|
|
|
|
if (_THIS_IS_MUSIC_BLOCKS_) {
|
|
// Load the synth for this drum
|
|
this.logo.synth.loadSynth('kick');
|
|
}
|
|
break;
|
|
case 'action':
|
|
case 'hat':
|
|
blkData[4][0] = null;
|
|
blkData[4][3] = null;
|
|
this._makeNewBlockWithConnections('action', blockOffset, blkData[4], null, null, collapsed);
|
|
break;
|
|
|
|
// Named boxes and dos need private data.
|
|
case 'namedbox':
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].privateData = value;
|
|
that.blockList[thisBlock].value = null;
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('namedbox', blockOffset, blkData[4], postProcess, [thisBlock, value]);
|
|
break;
|
|
case 'namedarg':
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].privateData = value;
|
|
that.blockList[thisBlock].value = null;
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('namedarg', blockOffset, blkData[4], postProcess, [thisBlock, value]);
|
|
break;
|
|
case 'namedcalc':
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].privateData = value;
|
|
that.blockList[thisBlock].value = null;
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('namedcalc', blockOffset, blkData[4], postProcess, [thisBlock, value]);
|
|
break;
|
|
case 'nameddo':
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].privateData = value;
|
|
that.blockList[thisBlock].value = null;
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('nameddo', blockOffset, blkData[4], postProcess, [thisBlock, value]);
|
|
break;
|
|
|
|
// Arg clamps may need extra slots added.
|
|
case 'doArg':
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var extraSlots = args[1].length - 4;
|
|
if (extraSlots > 0) {
|
|
var slotList = that.blockList[thisBlock].argClampSlots;
|
|
for (var i = 0; i < extraSlots; i++) {
|
|
slotList.push(1);
|
|
that._newLocalArgBlock(slotList.length);
|
|
that.blockList[thisBlock].connections.push(null);
|
|
}
|
|
that.blockList[thisBlock].updateArgSlots(slotList);
|
|
for (var i = 0; i < args[1].length; i++) {
|
|
if (args[1][i] != null) {
|
|
that.blockList[thisBlock].connections[i] = args[1][i] + firstBlock;
|
|
} else {
|
|
that.blockList[thisBlock].connections[i] = args[1][i];
|
|
}
|
|
}
|
|
}
|
|
that._checkArgClampBlocks.push(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('doArg', blockOffset, blkData[4], postProcess, [thisBlock, blkData[4]]);
|
|
break;
|
|
case 'nameddoArg':
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].privateData = value;
|
|
that.blockList[thisBlock].value = null;
|
|
var extraSlots = args[2].length - 3;
|
|
if (extraSlots > 0) {
|
|
var slotList = that.blockList[thisBlock].argClampSlots;
|
|
for (var i = 0; i < extraSlots; i++) {
|
|
slotList.push(1);
|
|
that._newLocalArgBlock(slotList.length);
|
|
that.blockList[thisBlock].connections.push(null);
|
|
}
|
|
that.blockList[thisBlock].updateArgSlots(slotList);
|
|
for (var i = 0; i < args[2].length; i++) {
|
|
if (args[2][i] != null) {
|
|
that.blockList[thisBlock].connections[i] = args[2][i] + firstBlock;
|
|
} else {
|
|
that.blockList[thisBlock].connections[i] = args[2][i];
|
|
}
|
|
}
|
|
}
|
|
that._checkArgClampBlocks.push(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('nameddoArg', blockOffset, blkData[4], postProcess, [thisBlock, value, blkData[4]]);
|
|
break;
|
|
case 'calcArg':
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var extraSlots = args[1].length - 3;
|
|
if (extraSlots > 0) {
|
|
var slotList = that.blockList[thisBlock].argClampSlots;
|
|
for (var i = 0; i < extraSlots; i++) {
|
|
slotList.push(1);
|
|
that._newLocalArgBlock(slotList.length);
|
|
that.blockList[thisBlock].connections.push(null);
|
|
}
|
|
that.blockList[thisBlock].updateArgSlots(slotList);
|
|
for (var i = 0; i < args[1].length; i++) {
|
|
if (args[1][i] != null) {
|
|
that.blockList[thisBlock].connections[i] = args[1][i] + firstBlock;
|
|
} else {
|
|
that.blockList[thisBlock].connections[i] = args[1][i];
|
|
}
|
|
}
|
|
}
|
|
that._checkArgClampBlocks.push(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('calcArg', blockOffset, blkData[4], postProcess, [thisBlock, blkData[4]]);
|
|
break;
|
|
case 'namedcalcArg':
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].privateData = value;
|
|
that.blockList[thisBlock].value = null;
|
|
var extraSlots = args[2].length - 2;
|
|
if (extraSlots > 0) {
|
|
var slotList = that.blockList[thisBlock].argClampSlots;
|
|
for (var i = 0; i < extraSlots; i++) {
|
|
slotList.push(1);
|
|
that._newLocalArgBlock(slotList.length);
|
|
that.blockList[thisBlock].connections.push(null);
|
|
}
|
|
that.blockList[thisBlock].updateArgSlots(slotList);
|
|
for (var i = 0; i < args[2].length; i++) {
|
|
if (args[2][i] != null) {
|
|
that.blockList[thisBlock].connections[i] = args[2][i] + firstBlock;
|
|
} else {
|
|
that.blockList[thisBlock].connections[i] = args[2][i];
|
|
}
|
|
}
|
|
}
|
|
that._checkArgClampBlocks.push(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('namedcalcArg', blockOffset, blkData[4], postProcess, [thisBlock, value, blkData[4]]);
|
|
break;
|
|
|
|
// Value blocks need a default value set.
|
|
case 'number':
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = Number(value);
|
|
that.updateBlockText(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
|
|
break;
|
|
case 'text':
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
that.updateBlockText(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
|
|
break;
|
|
case 'solfege':
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
that.updateBlockText(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
|
|
break;
|
|
case 'eastindiansolfege':
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
that.updateBlockText(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
|
|
break;
|
|
case 'notename':
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
that.updateBlockText(thisBlock);
|
|
};
|
|
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
|
|
break;
|
|
case 'modename':
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
that.updateBlockText(thisBlock);
|
|
};
|
|
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
|
|
break;
|
|
case 'drumname':
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
that.updateBlockText(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
|
|
|
|
if (_THIS_IS_MUSIC_BLOCKS_) {
|
|
// Load the synth for this drum
|
|
this.logo.synth.loadSynth(getDrumSynthName(value));
|
|
}
|
|
break;
|
|
case 'voicename':
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
that.updateBlockText(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
|
|
|
|
if (_THIS_IS_MUSIC_BLOCKS_) {
|
|
// Load the synth for this voice
|
|
this.logo.synth.loadSynth(getVoiceSynthName(value));
|
|
}
|
|
break;
|
|
case 'media':
|
|
// Load a thumbnail into a media blocks.
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = value;
|
|
if (value != null) {
|
|
// Load artwork onto media block.
|
|
that.blockList[thisBlock].loadThumbnail(null);
|
|
}
|
|
};
|
|
|
|
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
|
|
break;
|
|
case 'camera':
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = CAMERAVALUE;
|
|
};
|
|
|
|
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
|
|
break;
|
|
case 'video':
|
|
postProcess = function (args) {
|
|
var thisBlock = args[0];
|
|
var value = args[1];
|
|
that.blockList[thisBlock].value = VIDEOVALUE;
|
|
};
|
|
|
|
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
|
|
break;
|
|
|
|
// Define some constants for legacy blocks for
|
|
// backward compatibility with Python projects.
|
|
case 'red':
|
|
case 'black':
|
|
postProcess = function (thisBlock) {
|
|
that.blockList[thisBlock].value = 0;
|
|
that.updateBlockText(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
|
|
break;
|
|
case 'white':
|
|
postProcess = function (thisBlock) {
|
|
that.blockList[thisBlock].value = 100;
|
|
that.updateBlockText(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
|
|
break;
|
|
case 'orange':
|
|
postProcess = function (thisBlock) {
|
|
that.blockList[thisBlock].value = 10;
|
|
that.updateBlockText(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
|
|
break;
|
|
case 'yellow':
|
|
postProcess = function (thisBlock) {
|
|
that.blockList[thisBlock].value = 20;
|
|
that.updateBlockText(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
|
|
break;
|
|
case 'green':
|
|
postProcess = function (thisBlock) {
|
|
that.blockList[thisBlock].value = 40;
|
|
that.updateBlockText(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
|
|
break;
|
|
case 'blue':
|
|
postProcess = function (thisBlock) {
|
|
that.blockList[thisBlock].value = 70;
|
|
that.updateBlockText(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
|
|
break;
|
|
case 'leftpos':
|
|
postProcess = function (thisBlock) {
|
|
that.blockList[thisBlock].value = -(canvas.width / 2);
|
|
that.updateBlockText(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
|
|
break;
|
|
case 'rightpos':
|
|
postProcess = function (thisBlock) {
|
|
that.blockList[thisBlock].value = (canvas.width / 2);
|
|
that.updateBlockText(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
|
|
break;
|
|
case 'toppos':
|
|
postProcess = function (thisBlock) {
|
|
that.blockList[thisBlock].value = (canvas.height / 2);
|
|
that.updateBlockText(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
|
|
break;
|
|
case 'botpos':
|
|
case 'bottompos':
|
|
postProcess = function (thisBlock) {
|
|
that.blockList[thisBlock].value = -(canvas.height / 2);
|
|
that.updateBlockText(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
|
|
break;
|
|
case 'width':
|
|
postProcess = function (thisBlock) {
|
|
that.blockList[thisBlock].value = canvas.width;
|
|
that.updateBlockText(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
|
|
break;
|
|
case 'height':
|
|
postProcess = function (thisBlock) {
|
|
that.blockList[thisBlock].value = canvas.height;
|
|
that.updateBlockText(thisBlock);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
|
|
break;
|
|
case 'loadFile':
|
|
postProcess = function (args) {
|
|
that.blockList[args[0]].value = args[1];
|
|
that.updateBlockText(args[0]);
|
|
};
|
|
|
|
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
|
|
break;
|
|
default:
|
|
// Check that name is in the proto list
|
|
if (!name in this.protoBlockDict || this.protoBlockDict[name] == null) {
|
|
// Lots of assumptions here.
|
|
// TODO: figure out if it is a flow or an arg block.
|
|
// Substitute a NOP block for an unknown block.
|
|
n = blkData[4].length;
|
|
console.log(n + ': substituting nop block for ' + name);
|
|
switch (n) {
|
|
case 1:
|
|
name = 'nopValueBlock';
|
|
break;
|
|
case 2:
|
|
name = 'nopZeroArgBlock';
|
|
break;
|
|
case 3:
|
|
name = 'nopOneArgBlock';
|
|
break;
|
|
case 4:
|
|
name = 'nopTwoArgBlock';
|
|
break;
|
|
case 5:
|
|
default:
|
|
name = 'nopThreeArgBlock';
|
|
break;
|
|
}
|
|
}
|
|
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], null);
|
|
break;
|
|
}
|
|
|
|
if (thisBlock === this.blockList.length - 1) {
|
|
if (this.blockList[thisBlock].connections[0] == null) {
|
|
this.blockList[thisBlock].container.x = blkData[2];
|
|
this.blockList[thisBlock].container.y = blkData[3];
|
|
this._adjustTheseDocks.push(thisBlock);
|
|
if (blkData[4][0] == null) {
|
|
this._adjustTheseStacks.push(thisBlock);
|
|
}
|
|
if (blkData[2] < 0 || blkData[3] < 0 || blkData[2] > canvas.width || blkData[3] > canvas.height) {
|
|
this._homeButtonContainers[0].visible = true;
|
|
this._homeButtonContainers[1].visible = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
this.cleanupAfterLoad = function (name) {
|
|
// If all the blocks are loaded, we can make the final adjustments.
|
|
this._loadCounter -= 1;
|
|
if (this._loadCounter > 0) {
|
|
return;
|
|
}
|
|
|
|
this._findDrumURLs();
|
|
|
|
this.updateBlockPositions();
|
|
|
|
this._cleanupStacks();
|
|
|
|
for (var i = 0; i < this.blocksToCollapse.length; i++) {
|
|
this.blockList[this.blocksToCollapse[i]].collapseToggle();
|
|
}
|
|
|
|
this.blocksToCollapse = [];
|
|
|
|
for (var blk = 0; blk < this.blockList.length; blk++) {
|
|
if (this.blockList[blk].collapseContainer != null) {
|
|
this.blockList[blk].collapseContainer.x = this.blockList[blk].container.x + COLLAPSEBUTTONXOFF * (this.blockList[blk].protoblock.scale / 2);
|
|
this.blockList[blk].collapseContainer.y = this.blockList[blk].container.y + COLLAPSEBUTTONYOFF * (this.blockList[blk].protoblock.scale / 2);
|
|
}
|
|
}
|
|
|
|
this.refreshCanvas();
|
|
|
|
// Do a final check on the action and boxes palettes.
|
|
var updatePalettes = false;
|
|
for (var blk = 0; blk < this.blockList.length; blk++) {
|
|
if (!this.blockList[blk].trash && this.blockList[blk].name === 'action') {
|
|
var myBlock = this.blockList[blk];
|
|
var arg = null;
|
|
var c = myBlock.connections[1];
|
|
if (c != null && this.blockList[c].value !== _('action')) {
|
|
if (this.newNameddoBlock(this.blockList[c].value, this.actionHasReturn(blk), this.actionHasArgs(blk))) {
|
|
updatePalettes = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (updatePalettes) {
|
|
this.palettes.hide();
|
|
this.palettes.updatePalettes('action');
|
|
// this.palettes.dict['action'].hide();
|
|
this.palettes.show();
|
|
}
|
|
|
|
var updatePalettes = false;
|
|
for (var blk = 0; blk < this.blockList.length; blk++) {
|
|
if (!this.blockList[blk].trash && this.blockList[blk].name === 'storein') {
|
|
var myBlock = this.blockList[blk];
|
|
var arg = null;
|
|
var c = myBlock.connections[1];
|
|
if (c != null && this.blockList[c].value !== _('box')) {
|
|
var name = this.blockList[c].value;
|
|
// Is there an old block with this name still around?
|
|
if (this.protoBlockDict['myStorein_' + name] == undefined) {
|
|
console.log('adding new storein block ' + name);
|
|
this.newNamedboxBlock(this.blockList[c].value);
|
|
this.newStoreinBlock(this.blockList[c].value);
|
|
updatePalettes = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (updatePalettes) {
|
|
// Do this update on a slight delay so as not to collide with
|
|
// the actions update.
|
|
var that = this;
|
|
setTimeout(function () {
|
|
that.palettes.hide();
|
|
that.palettes.updatePalettes('boxes');
|
|
// that.palettes.dict['boxes'].hide();
|
|
that.palettes.show();
|
|
}, 1500);
|
|
}
|
|
console.log("Finished block loading");
|
|
var myCustomEvent = new Event('finishedLoading');
|
|
document.dispatchEvent(myCustomEvent);
|
|
};
|
|
|
|
this._cleanupStacks = function () {
|
|
if (this._checkArgClampBlocks.length > 0) {
|
|
// We make multiple passes because we need to account for nesting.
|
|
// FIXME: needs to be interwoven with TwoArgBlocks check.
|
|
for (var i = 0; i < this._checkArgClampBlocks.length; i++) {
|
|
for (var b = 0; b < this._checkArgClampBlocks.length; b++) {
|
|
this._adjustArgClampBlock([this._checkArgClampBlocks[b]]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this._checkTwoArgBlocks.length > 0) {
|
|
// We make multiple passes because we need to account for nesting.
|
|
for (var i = 0; i < this._checkTwoArgBlocks.length; i++) {
|
|
for (var b = 0; b < this._checkTwoArgBlocks.length; b++) {
|
|
this._adjustExpandableTwoArgBlock([this._checkTwoArgBlocks[b]]);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (var blk = 0; blk < this._adjustTheseDocks.length; blk++) {
|
|
// console.log('Adjust Docks: ' + this.blockList[this._adjustTheseDocks[blk]].name);
|
|
this.adjustDocks(this._adjustTheseDocks[blk], true);
|
|
// blockBlocks._expandTwoArgs();
|
|
this._expandClamps();
|
|
}
|
|
|
|
for (var blk = 0; blk < this._adjustTheseStacks.length; blk++) {
|
|
// console.log('Adjust Stack: ' + this.blockList[this._adjustTheseStacks[blk]].name);
|
|
this.raiseStackToTop(this._adjustTheseStacks[blk]);
|
|
}
|
|
};
|
|
|
|
this.actionHasReturn = function (blk) {
|
|
// Look for a return block in an action stack.
|
|
if (this.blockList[blk].name !== 'action') {
|
|
return false;
|
|
}
|
|
this.findDragGroup(blk);
|
|
for (var b = 0; b < this.dragGroup.length; b++) {
|
|
if (this.blockList[this.dragGroup[b]].name === 'return') {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
this.actionHasArgs = function (blk) {
|
|
// Look for an arg blocks in an action stack.
|
|
if (this.blockList[blk].name !== 'action') {
|
|
return false;
|
|
}
|
|
this.findDragGroup(blk);
|
|
for (var b = 0; b < this.dragGroup.length; b++) {
|
|
if (this.blockList[this.dragGroup[b]].name === 'arg' || this.blockList[this.dragGroup[b]].name === 'namedarg') {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
this.raiseStackToTop = function (blk) {
|
|
// Move the stack associated with blk to the top.
|
|
var topBlk = this.findTopBlock(blk);
|
|
this.findDragGroup(topBlk);
|
|
|
|
var z = this.stage.getNumChildren() - 1;
|
|
for (var b = 0; b < this.dragGroup.length; b++) {
|
|
this.stage.setChildIndex(this.blockList[this.dragGroup[b]].container, z);
|
|
z -= 1;
|
|
}
|
|
|
|
this.refreshCanvas;
|
|
};
|
|
|
|
this.deleteActionBlock = function (myBlock) {
|
|
var actionArg = this.blockList[myBlock.connections[1]];
|
|
if (actionArg) {
|
|
var actionName = actionArg.value;
|
|
for (var blk = 0; blk < this.blockList.length; blk++) {
|
|
var myBlock = this.blockList[blk];
|
|
var blkParent = this.blockList[myBlock.connections[0]];
|
|
if (blkParent == null) {
|
|
continue;
|
|
}
|
|
|
|
if (['namedcalc', 'calc', 'nameddo', 'do', 'action'].indexOf(blkParent.name) !== -1) {
|
|
continue;
|
|
}
|
|
|
|
var blockValue = myBlock.value;
|
|
if (blockValue === _('action')) {
|
|
continue;
|
|
}
|
|
|
|
if (blockValue === actionName) {
|
|
blkParent.hide();
|
|
myBlock.hide();
|
|
myBlock.trash = true;
|
|
blkParent.trash = true;
|
|
}
|
|
}
|
|
|
|
// Avoid palette refreash race condition.
|
|
this.deleteActionTimeout += 500;
|
|
var timeout = this.deleteActionTimeout;
|
|
var that = this;
|
|
setTimeout(function () {
|
|
that.deleteActionTimeout -= 500;
|
|
that.palettes.removeActionPrototype(actionName);
|
|
}, timeout);
|
|
}
|
|
};
|
|
|
|
this.sendStackToTrash = function (myBlock) {
|
|
// First, hide the palettes as they will need updating.
|
|
for (var name in this.palettes.dict) {
|
|
this.palettes.dict[name].hideMenu(true);
|
|
}
|
|
|
|
this.refreshCanvas();
|
|
|
|
var thisBlock = this.blockList.indexOf(myBlock);
|
|
|
|
// Add this block to the list of blocks in the trash so we can
|
|
// undo this action.
|
|
this.trashStacks.push(thisBlock);
|
|
|
|
// Disconnect block.
|
|
var parentBlock = myBlock.connections[0];
|
|
if (parentBlock != null) {
|
|
for (var c in this.blockList[parentBlock].connections) {
|
|
if (this.blockList[parentBlock].connections[c] === thisBlock) {
|
|
this.blockList[parentBlock].connections[c] = null;
|
|
break;
|
|
}
|
|
}
|
|
myBlock.connections[0] = null;
|
|
|
|
// Add default block if user deletes all blocks from inside the note block
|
|
this.addDefaultBlock(parentBlock, thisBlock);
|
|
}
|
|
|
|
if (myBlock.name === 'start' || myBlock.name === 'drum') {
|
|
turtle = myBlock.value;
|
|
var turtleNotInTrash = 0;
|
|
for (var i = 0; i < this.turtles.turtleList.length; i++) {
|
|
if (!this.turtles.turtleList[i].trash) {
|
|
turtleNotInTrash += 1;
|
|
}
|
|
}
|
|
if (turtle != null && turtleNotInTrash > 1) {
|
|
console.log('putting turtle ' + turtle + ' in the trash');
|
|
this.turtles.turtleList[turtle].trash = true;
|
|
this.turtles.turtleList[turtle].container.visible = false;
|
|
} else {
|
|
this.errorMsg("You must always have at least one start block");
|
|
console.log('null turtle');
|
|
return;
|
|
}
|
|
} else if (myBlock.name === 'action') {
|
|
if (!myBlock.trash) {
|
|
this.deleteActionBlock(myBlock);
|
|
}
|
|
}
|
|
|
|
// put drag group in trash
|
|
this.findDragGroup(thisBlock);
|
|
for (var b = 0; b < this.dragGroup.length; b++) {
|
|
var blk = this.dragGroup[b];
|
|
// console.log('putting ' + this.blockList[blk].name + ' in the trash');
|
|
this.blockList[blk].trash = true;
|
|
this.blockList[blk].hide();
|
|
this.refreshCanvas();
|
|
}
|
|
|
|
// Adjust the stack from which we just deleted blocks.
|
|
if (parentBlock != null) {
|
|
var topBlk = this.findTopBlock(parentBlock);
|
|
this.findDragGroup(topBlk);
|
|
|
|
// We need to track two-arg blocks in case they need expanding.
|
|
this._checkTwoArgBlocks = [];
|
|
|
|
// And arg clamp blocks in case they need expanding.
|
|
this._checkArgClampBlocks = [];
|
|
|
|
for (var b = 0; b < this.dragGroup.length; b++) {
|
|
var blk = this.dragGroup[b];
|
|
var myBlock = this.blockList[blk];
|
|
if (myBlock.isTwoArgBlock()) {
|
|
this._checkTwoArgBlocks.push(blk);
|
|
} else if (myBlock.isArgBlock() && myBlock.isExpandableBlock() || myBlock.isArgClamp()) {
|
|
this._checkTwoArgBlocks.push(blk);
|
|
} else if (['clamp', 'argclamp', 'argclamparg', 'doubleclamp', 'argflowclamp'].indexOf(myBlock.protoblock.style) !== -1) {
|
|
this._checkArgClampBlocks.push(blk);
|
|
}
|
|
}
|
|
this._cleanupStacks();
|
|
this.refreshCanvas();
|
|
}
|
|
};
|
|
|
|
return this;
|
|
};
|