mirror of
https://github.com/google/blockly.git
synced 2026-01-07 17:10:11 +01:00
Propagate the visible state when blocks connect (#2003)
* Propagate the visible state when blocks connect This fixes #1967. In rendered connections when connecting: - If the superior connection is hidden this hides the newly connected block. - If the superior connection isn't hidden it makes sure the block is visible. In rendered connections when disconnecting: - If the superior connection is hidden, make the disconnected block stack visible. TODO before review: - write tests. - update collapsed message * Add missing overrides * Add tests for hidden connections and fix a bug while disposing
This commit is contained in:
@@ -316,6 +316,10 @@ Blockly.BlockSvg.prototype.getHeightWidth = function() {
|
||||
* If true, also render block's parent, grandparent, etc. Defaults to true.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.render = function(opt_bubble) {
|
||||
if (!this.workspace) {
|
||||
// This block is being deleted so don't try to render it.
|
||||
return;
|
||||
}
|
||||
Blockly.Field.startCache();
|
||||
this.rendered = true;
|
||||
|
||||
|
||||
@@ -445,6 +445,10 @@ Blockly.Connection.prototype.connect = function(otherConnection) {
|
||||
return;
|
||||
}
|
||||
this.checkConnection_(otherConnection);
|
||||
var eventGroup = Blockly.Events.getGroup();
|
||||
if (!eventGroup) {
|
||||
Blockly.Events.setGroup(true);
|
||||
}
|
||||
// Determine which block is superior (higher in the source stack).
|
||||
if (this.isSuperior()) {
|
||||
// Superior block.
|
||||
@@ -453,6 +457,9 @@ Blockly.Connection.prototype.connect = function(otherConnection) {
|
||||
// Inferior block.
|
||||
otherConnection.connect_(this);
|
||||
}
|
||||
if (!eventGroup) {
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -542,8 +549,16 @@ Blockly.Connection.prototype.disconnect = function() {
|
||||
childBlock = this.sourceBlock_;
|
||||
parentConnection = otherConnection;
|
||||
}
|
||||
|
||||
var eventGroup = Blockly.Events.getGroup();
|
||||
if (!eventGroup) {
|
||||
Blockly.Events.setGroup(true);
|
||||
}
|
||||
this.disconnectInternal_(parentBlock, childBlock);
|
||||
parentConnection.respawnShadow_();
|
||||
if (!eventGroup) {
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -225,6 +225,7 @@ Blockly.RenderedConnection.prototype.highlight = function() {
|
||||
* attached to this connection. This happens when a block is expanded.
|
||||
* Also unhides down-stream comments.
|
||||
* @return {!Array.<!Blockly.Block>} List of blocks to render.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.RenderedConnection.prototype.unhideAll = function() {
|
||||
this.setHidden(false);
|
||||
@@ -272,6 +273,7 @@ Blockly.RenderedConnection.prototype.unhighlight = function() {
|
||||
/**
|
||||
* Set whether this connections is hidden (not tracked in a database) or not.
|
||||
* @param {boolean} hidden True if connection is hidden.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.RenderedConnection.prototype.setHidden = function(hidden) {
|
||||
this.hidden_ = hidden;
|
||||
@@ -286,6 +288,7 @@ Blockly.RenderedConnection.prototype.setHidden = function(hidden) {
|
||||
* Hide this connection, as well as all down-stream connections on any block
|
||||
* attached to this connection. This happens when a block is collapsed.
|
||||
* Also hides down-stream comments.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.RenderedConnection.prototype.hideAll = function() {
|
||||
this.setHidden(true);
|
||||
@@ -324,6 +327,49 @@ Blockly.RenderedConnection.prototype.isConnectionAllowed = function(candidate,
|
||||
candidate);
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect this connection to another connection.
|
||||
* @param {!Blockly.Connection} otherConnection Connection to connect to.
|
||||
* @override
|
||||
*/
|
||||
Blockly.RenderedConnection.prototype.connect = function(otherConnection) {
|
||||
Blockly.RenderedConnection.superClass_.connect.call(this, otherConnection);
|
||||
|
||||
// This is a quick check to make sure we aren't doing unecessary work.
|
||||
if (this.hidden_ || otherConnection.hidden_) {
|
||||
var superiorConnection = this.isSuperior() ? this : otherConnection;
|
||||
if (superiorConnection.hidden_) {
|
||||
superiorConnection.hideAll();
|
||||
} else {
|
||||
superiorConnection.unhideAll();
|
||||
}
|
||||
|
||||
var renderedBlock = superiorConnection.targetBlock();
|
||||
var display = superiorConnection.hidden_ ? 'none' : 'block';
|
||||
renderedBlock.getSvgRoot().style.display = display;
|
||||
renderedBlock.rendered = !superiorConnection.hidden_;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnect this connection.
|
||||
* @override
|
||||
*/
|
||||
Blockly.RenderedConnection.prototype.disconnect = function() {
|
||||
var superiorConnection = this.isSuperior() ? this : this.targetConnection;
|
||||
if (this.targetConnection && superiorConnection.hidden_) {
|
||||
superiorConnection.unhideAll();
|
||||
var renderedBlock = superiorConnection.targetBlock();
|
||||
renderedBlock.getSvgRoot().style.display = 'block';
|
||||
renderedBlock.rendered = true;
|
||||
|
||||
// Set the hidden state for the connection back to true so shadow blocks
|
||||
// will be hidden.
|
||||
superiorConnection.setHidden(true);
|
||||
}
|
||||
Blockly.RenderedConnection.superClass_.disconnect.call(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnect two blocks that are connected by this connection.
|
||||
* @param {!Blockly.Block} parentBlock The superior block.
|
||||
@@ -361,7 +407,7 @@ Blockly.RenderedConnection.prototype.respawnShadow_ = function() {
|
||||
}
|
||||
blockShadow.initSvg();
|
||||
blockShadow.render(false);
|
||||
if (parentBlock.rendered) {
|
||||
if (parentBlock.rendered && !this.hidden_) {
|
||||
parentBlock.render();
|
||||
}
|
||||
}
|
||||
|
||||
322
tests/mocha/connection_test.js
Normal file
322
tests/mocha/connection_test.js
Normal file
@@ -0,0 +1,322 @@
|
||||
|
||||
|
||||
suite('Connections', function() {
|
||||
|
||||
suite('Rendered', function() {
|
||||
function assertAllConnectionsHidden(block) {
|
||||
assertAllConnectionsHiddenState(block, true);
|
||||
}
|
||||
function assertAllConnectionsVisible(block) {
|
||||
assertAllConnectionsHiddenState(block, false);
|
||||
}
|
||||
function assertAllConnectionsHiddenState(block, hidden) {
|
||||
var connections = block.getConnections_(true);
|
||||
for (var i = 0; i < connections.length; i++) {
|
||||
var connection = connections[i];
|
||||
if (connection.type == Blockly.PREVIOUS_STATEMENT
|
||||
|| connection.type == Blockly.OUTPUT_VALUE) {
|
||||
// Only superior connections on inputs get hidden
|
||||
continue;
|
||||
}
|
||||
if (block.nextConnection && connection === block.nextConnection) {
|
||||
// The next connection is not hidden when collapsed
|
||||
continue;
|
||||
}
|
||||
assertEquals('Connection ' + i + ' failed', hidden, connections[i].hidden_)
|
||||
}
|
||||
}
|
||||
|
||||
setup(function() {
|
||||
Blockly.defineBlocksWithJsonArray([{
|
||||
"type": "stack_block",
|
||||
"message0": "",
|
||||
"previousStatement": null,
|
||||
"nextStatement": null
|
||||
},
|
||||
{
|
||||
"type": "row_block",
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "INPUT"
|
||||
}
|
||||
],
|
||||
"output": null
|
||||
},
|
||||
{
|
||||
"type": "inputs_block",
|
||||
"message0": "%1 %2",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "INPUT"
|
||||
},
|
||||
{
|
||||
"type": "input_statement",
|
||||
"name": "STATEMENT"
|
||||
}
|
||||
],
|
||||
"previousStatement": null,
|
||||
"nextStatement": null
|
||||
},]);
|
||||
|
||||
var toolbox = document.getElementById('toolbox-connections');
|
||||
this.workspace = Blockly.inject('blocklyDiv', {toolbox: toolbox});
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
delete Blockly.Blocks['stack_block'];
|
||||
delete Blockly.Blocks['row_block'];
|
||||
delete Blockly.Blocks['inputs_block'];
|
||||
|
||||
this.workspace.dispose();
|
||||
});
|
||||
|
||||
suite('Row collapsing', function() {
|
||||
setup(function() {
|
||||
var blockA = this.workspace.newBlock('row_block');
|
||||
var blockB = this.workspace.newBlock('row_block');
|
||||
var blockC = this.workspace.newBlock('row_block');
|
||||
|
||||
blockA.inputList[0].connection.connect(blockB.outputConnection);
|
||||
blockA.setCollapsed(true);
|
||||
|
||||
assertEquals(blockA, blockB.getParent());
|
||||
assertNull(blockC.getParent())
|
||||
assertTrue(blockA.isCollapsed());
|
||||
assertAllConnectionsHidden(blockA);
|
||||
assertAllConnectionsHidden(blockB);
|
||||
assertAllConnectionsVisible(blockC);
|
||||
|
||||
this.blocks = {
|
||||
A: blockA,
|
||||
B: blockB,
|
||||
C: blockC
|
||||
};
|
||||
});
|
||||
|
||||
test('Add to end', function() {
|
||||
var blocks = this.blocks;
|
||||
blocks.B.inputList[0].connection.connect(blocks.C.outputConnection);
|
||||
assertAllConnectionsHidden(blocks.C);
|
||||
});
|
||||
|
||||
test('Add to end w/inferior', function() {
|
||||
var blocks = this.blocks;
|
||||
blocks.C.outputConnection.connect(blocks.B.inputList[0].connection);
|
||||
assertAllConnectionsHidden(blocks.C);
|
||||
});
|
||||
|
||||
test('Add to middle', function() {
|
||||
var blocks = this.blocks;
|
||||
blocks.A.inputList[0].connection.connect(blocks.C.outputConnection);
|
||||
assertAllConnectionsHidden(blocks.C);
|
||||
});
|
||||
|
||||
test('Add to middle w/inferior', function() {
|
||||
var blocks = this.blocks;
|
||||
blocks.C.outputConnection.connect(blocks.A.inputList[0].connection);
|
||||
assertAllConnectionsHidden(blocks.C);
|
||||
});
|
||||
|
||||
test('Remove simple', function() {
|
||||
var blocks = this.blocks;
|
||||
blocks.B.unplug();
|
||||
assertAllConnectionsVisible(blocks.B);
|
||||
});
|
||||
|
||||
test('Remove middle', function() {
|
||||
var blocks = this.blocks;
|
||||
blocks.B.inputList[0].connection.connect(blocks.C.outputConnection);
|
||||
blocks.B.unplug(false);
|
||||
assertAllConnectionsVisible(blocks.B);
|
||||
assertAllConnectionsVisible(blocks.C);
|
||||
});
|
||||
|
||||
test('Remove middle healing', function() {
|
||||
var blocks = this.blocks;
|
||||
blocks.B.inputList[0].connection.connect(blocks.C.outputConnection);
|
||||
blocks.B.unplug(true);
|
||||
assertAllConnectionsVisible(blocks.B);
|
||||
assertAllConnectionsHidden(blocks.C);
|
||||
});
|
||||
|
||||
test('Add before', function() {
|
||||
var blocks = this.blocks;
|
||||
blocks.C.inputList[0].connection.connect(blocks.A.outputConnection);
|
||||
// Connecting a collapsed block to another block doesn't change any hidden state
|
||||
assertAllConnectionsHidden(blocks.A);
|
||||
assertAllConnectionsVisible(blocks.C);
|
||||
});
|
||||
|
||||
test('Remove front', function() {
|
||||
var blocks = this.blocks;
|
||||
blocks.B.inputList[0].connection.connect(blocks.C.outputConnection);
|
||||
blocks.A.inputList[0].connection.disconnect();
|
||||
assertTrue(blocks.A.isCollapsed());
|
||||
assertAllConnectionsHidden(blocks.A);
|
||||
assertAllConnectionsVisible(blocks.B);
|
||||
assertAllConnectionsVisible(blocks.C);
|
||||
});
|
||||
|
||||
test('Uncollapse', function() {
|
||||
var blocks = this.blocks;
|
||||
blocks.B.inputList[0].connection.connect(blocks.C.outputConnection);
|
||||
blocks.A.setCollapsed(false);
|
||||
assertFalse(blocks.A.isCollapsed());
|
||||
assertAllConnectionsVisible(blocks.A);
|
||||
assertAllConnectionsVisible(blocks.B);
|
||||
assertAllConnectionsVisible(blocks.C);
|
||||
});
|
||||
});
|
||||
suite('Statement collapsing', function() {
|
||||
setup(function() {
|
||||
var blockA = this.workspace.newBlock('inputs_block');
|
||||
var blockB = this.workspace.newBlock('inputs_block');
|
||||
var blockC = this.workspace.newBlock('inputs_block');
|
||||
|
||||
blockA.getInput('STATEMENT').connection.connect(blockB.previousConnection);
|
||||
blockA.setCollapsed(true);
|
||||
|
||||
assertEquals(blockA, blockB.getParent());
|
||||
assertNull(blockC.getParent())
|
||||
assertTrue(blockA.isCollapsed());
|
||||
assertAllConnectionsHidden(blockA);
|
||||
assertAllConnectionsHidden(blockB);
|
||||
assertAllConnectionsVisible(blockC);
|
||||
|
||||
this.blocks = {
|
||||
A: blockA,
|
||||
B: blockB,
|
||||
C: blockC
|
||||
};
|
||||
});
|
||||
|
||||
test('Add to statement', function() {
|
||||
var blocks = this.blocks;
|
||||
blocks.B.getInput('STATEMENT').connection.connect(blocks.C.previousConnection);
|
||||
assertAllConnectionsHidden(blocks.C);
|
||||
});
|
||||
|
||||
test('Insert in statement', function() {
|
||||
var blocks = this.blocks;
|
||||
blocks.A.getInput('STATEMENT').connection.connect(blocks.C.previousConnection);
|
||||
assertAllConnectionsHidden(blocks.C);
|
||||
});
|
||||
|
||||
test('Add to hidden next', function() {
|
||||
var blocks = this.blocks;
|
||||
blocks.B.nextConnection.connect(blocks.C.previousConnection);
|
||||
assertAllConnectionsHidden(blocks.C);
|
||||
});
|
||||
|
||||
test('Remove simple', function() {
|
||||
var blocks = this.blocks;
|
||||
blocks.B.unplug();
|
||||
assertAllConnectionsVisible(blocks.B);
|
||||
});
|
||||
|
||||
test('Remove middle', function() {
|
||||
var blocks = this.blocks;
|
||||
blocks.B.nextConnection.connect(blocks.C.previousConnection);
|
||||
blocks.B.unplug(false);
|
||||
assertAllConnectionsVisible(blocks.B);
|
||||
assertAllConnectionsVisible(blocks.C);
|
||||
});
|
||||
|
||||
test('Remove middle healing', function() {
|
||||
var blocks = this.blocks;
|
||||
blocks.B.nextConnection.connect(blocks.C.previousConnection);
|
||||
blocks.B.unplug(true);
|
||||
assertAllConnectionsVisible(blocks.B);
|
||||
assertAllConnectionsHidden(blocks.C);
|
||||
});
|
||||
|
||||
test('Add before', function() {
|
||||
var blocks = this.blocks;
|
||||
blocks.C.getInput('STATEMENT').connection.connect(blocks.A.previousConnection);
|
||||
assertAllConnectionsHidden(blocks.A);
|
||||
assertAllConnectionsHidden(blocks.B);
|
||||
assertAllConnectionsVisible(blocks.C);
|
||||
});
|
||||
|
||||
test('Remove front', function() {
|
||||
var blocks = this.blocks;
|
||||
blocks.B.nextConnection.connect(blocks.C.previousConnection);
|
||||
blocks.A.getInput('STATEMENT').connection.disconnect();
|
||||
assertTrue(blocks.A.isCollapsed());
|
||||
assertAllConnectionsHidden(blocks.A);
|
||||
assertAllConnectionsVisible(blocks.B);
|
||||
assertAllConnectionsVisible(blocks.C);
|
||||
});
|
||||
|
||||
test('Uncollapse', function() {
|
||||
var blocks = this.blocks;
|
||||
blocks.B.nextConnection.connect(blocks.C.previousConnection);
|
||||
blocks.A.setCollapsed(false);
|
||||
assertFalse(blocks.A.isCollapsed());
|
||||
assertAllConnectionsVisible(blocks.A);
|
||||
assertAllConnectionsVisible(blocks.B);
|
||||
assertAllConnectionsVisible(blocks.C);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Collapsing with shadows', function() {
|
||||
setup(function() {
|
||||
var blockA = this.workspace.newBlock('inputs_block');
|
||||
var blockB = this.workspace.newBlock('inputs_block');
|
||||
var blockC = this.workspace.newBlock('inputs_block');
|
||||
var blockD = this.workspace.newBlock('row_block');
|
||||
|
||||
blockB.setShadow(true);
|
||||
var shadowStatement = Blockly.Xml.blockToDom(blockB, true /*noid*/);
|
||||
blockB.setShadow(false);
|
||||
|
||||
blockD.setShadow(true);
|
||||
var shadowValue = Blockly.Xml.blockToDom(blockD, true /*noid*/);
|
||||
blockD.setShadow(false);
|
||||
|
||||
var connection = blockA.getInput('STATEMENT').connection;
|
||||
connection.setShadowDom(shadowStatement);
|
||||
connection.connect(blockB.previousConnection);
|
||||
connection = blockA.getInput('INPUT').connection;
|
||||
connection.setShadowDom(shadowValue);
|
||||
connection.connect(blockD.outputConnection);
|
||||
blockA.setCollapsed(true);
|
||||
|
||||
assertEquals(blockA, blockB.getParent());
|
||||
assertNull(blockC.getParent())
|
||||
assertTrue(blockA.isCollapsed());
|
||||
assertAllConnectionsHidden(blockA);
|
||||
assertAllConnectionsHidden(blockB);
|
||||
assertAllConnectionsVisible(blockC);
|
||||
|
||||
this.blocks = {
|
||||
A: blockA,
|
||||
B: blockB,
|
||||
C: blockC,
|
||||
D: blockD
|
||||
};
|
||||
});
|
||||
|
||||
test('Reveal shadow statement', function() {
|
||||
var blocks = this.blocks;
|
||||
var connection = blocks.A.getInput('STATEMENT').connection;
|
||||
connection.disconnect();
|
||||
var shadowBlock = connection.targetBlock();
|
||||
assertTrue(shadowBlock.isShadow());
|
||||
assertAllConnectionsHidden(shadowBlock);
|
||||
})
|
||||
|
||||
test('Reveal shadow value', function() {
|
||||
var blocks = this.blocks;
|
||||
var connection = blocks.A.getInput('INPUT').connection;
|
||||
connection.disconnect();
|
||||
var shadowBlock = connection.targetBlock();
|
||||
assertTrue(shadowBlock.isShadow());
|
||||
assertAllConnectionsHidden(shadowBlock);
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -21,9 +21,16 @@
|
||||
<script src="test_helpers.js"></script>
|
||||
<script src="block_test.js"></script>
|
||||
<script src="event_test.js"></script>
|
||||
<script src="connection_test.js"></script>
|
||||
<script src="field_variable_test.js"></script>
|
||||
<script src="utils_test.js"></script>
|
||||
|
||||
<div id="blocklyDiv"></div>
|
||||
<xml id="toolbox-connections" style="display: none">
|
||||
<block type="stack_block"></block>
|
||||
<block type="row_block"></block>
|
||||
</xml>
|
||||
|
||||
<script>
|
||||
mocha.run();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user